Coverage for sources / agentsmgr / context.py: 56%
45 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 02:32 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 02:32 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
4#============================================================================#
5# #
6# Licensed under the Apache License, Version 2.0 (the "License"); #
7# you may not use this file except in compliance with the License. #
8# You may obtain a copy of the License at #
9# #
10# http://www.apache.org/licenses/LICENSE-2.0 #
11# #
12# Unless required by applicable law or agreed to in writing, software #
13# distributed under the License is distributed on an "AS IS" BASIS, #
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15# See the License for the specific language governing permissions and #
16# limitations under the License. #
17# #
18#============================================================================#
21''' Template rendering context normalization and tool mapping.
23 Provides context transformation for template rendering, including
24 hyphen-to-underscore normalization and coder-specific tool mapping.
25'''
28from . import __
29from . import exceptions as _exceptions
32ToolSpecification: __.typx.TypeAlias = (
33 str | dict[ str, __.typx.Any ] )
36_SEMANTIC_TOOLS_CLAUDE: dict[ str, str ] = {
37 'read': 'Read',
38 'edit': 'Edit',
39 'multi-edit': 'MultiEdit',
40 'write': 'Write',
41 'list-directory': 'LS',
42 'glob': 'Glob',
43 'grep': 'Grep',
44 'todo-write': 'TodoWrite',
45 'web-fetch': 'WebFetch',
46 'web-search': 'WebSearch',
47}
49def normalize_render_context(
50 context_data: __.cabc.Mapping[ str, __.typx.Any ],
51 coder_config: __.cabc.Mapping[ str, __.typx.Any ],
52) -> dict[ str, __.typx.Any ]:
53 ''' Normalizes template rendering context with tool mapping.
55 Transforms hyphenated keys to underscored keys, wraps configurations
56 in SimpleNamespace objects for dot-notation access, and maps
57 allowed-tools specifications to coder-specific syntax.
58 '''
59 coder_name = coder_config.get( 'name', 'unknown' )
60 normalized_context = {
61 key.replace( '-', '_' ): value
62 for key, value in context_data.items( ) }
63 if 'allowed_tools' in normalized_context: 63 ↛ 67line 63 didn't jump to line 67 because the condition on line 63 was always true
64 raw_tools = normalized_context[ 'allowed_tools' ]
65 normalized_context[ 'allowed_tools' ] = (
66 _map_tools_for_coder( raw_tools, coder_name ) )
67 context_namespace = __.types.SimpleNamespace( **normalized_context )
68 coder_namespace = __.types.SimpleNamespace( **coder_config )
69 return {
70 'context': context_namespace,
71 'coder': coder_namespace,
72 }
75def _map_tools_for_coder(
76 tool_specs: __.cabc.Sequence[ ToolSpecification ],
77 coder_name: str,
78) -> list[ str ]:
79 ''' Maps tool specifications to coder-specific syntax.
81 Dispatches to coder-specific mapping function based on coder name.
82 Returns empty list if coder is not supported.
83 '''
84 if coder_name == 'claude':
85 return _map_tools_claude( tool_specs )
86 return [ ]
89def _map_tools_claude(
90 tool_specs: __.cabc.Sequence[ ToolSpecification ]
91) -> list[ str ]:
92 ''' Maps tool specifications to Claude-specific syntax.
94 Handles three specification types:
95 - String literals (semantic names): 'read' → 'Read'
96 - Shell commands: { tool = 'shell', arguments, ... } → 'Bash(...)'
97 - MCP tools: { server, tool } → 'mcp__server__tool'
99 Returns tools sorted alphabetically for consistent output.
100 '''
101 mapped: list[ str ] = [ ]
102 for spec in tool_specs:
103 if isinstance( spec, str ): 103 ↛ 105line 103 didn't jump to line 105 because the condition on line 103 was always true
104 mapped.append( _map_semantic_tool_claude( spec ) )
105 elif isinstance( spec, dict ):
106 if 'server' in spec:
107 mapped.append( _map_mcp_tool_claude( spec ) )
108 elif spec.get( 'tool' ) == 'shell':
109 mapped.append( _map_shell_tool_claude( spec ) )
110 else:
111 raise _exceptions.ToolSpecificationInvalidity( str( spec ) )
112 else:
113 raise _exceptions.ToolSpecificationTypeInvalidity(
114 type( spec ).__name__
115 )
116 return sorted( mapped )
119def _map_semantic_tool_claude( tool_name: str ) -> str:
120 ''' Maps semantic tool name to Claude tool name.
122 Uses lookup table for known semantic names.
123 Raises ToolSpecificationInvalidity for unknown tools.
124 '''
125 if tool_name not in _SEMANTIC_TOOLS_CLAUDE: 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 raise _exceptions.ToolSpecificationInvalidity( tool_name )
127 return _SEMANTIC_TOOLS_CLAUDE[ tool_name ]
130def _map_shell_tool_claude( spec: dict[ str, __.typx.Any ] ) -> str:
131 ''' Maps shell command specification to Claude Bash tool syntax.
133 Format: { tool = 'shell', arguments = 'git status' }
134 → 'Bash(git status)'
136 With wildcard: { tool = 'shell', arguments = 'git pull',
137 allow-extra-arguments = true }
138 → 'Bash(git pull:*)'
139 '''
140 arguments = spec.get( 'arguments', '' )
141 allow_extra = spec.get( 'allow-extra-arguments', False )
142 if allow_extra:
143 return f"Bash({arguments}:*)"
144 return f"Bash({arguments})"
147def _map_mcp_tool_claude( spec: dict[ str, __.typx.Any ] ) -> str:
148 ''' Maps MCP tool specification to Claude MCP tool syntax.
150 Format: { server = 'librovore', tool = 'query-inventory' }
151 → 'mcp__librovore__query_inventory'
152 '''
153 server = spec.get( 'server', '' )
154 tool = spec.get( 'tool', '' )
155 tool_normalized = tool.replace( '-', '_' )
156 return f"mcp__{server}__{tool_normalized}"