codexa 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- semantic_code_intelligence/workspace/__init__.py +427 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Tool execution engine — validates, routes, and executes tool invocations.
|
|
2
|
+
|
|
3
|
+
Wraps the existing ``ToolRegistry`` with:
|
|
4
|
+
- Argument validation against ``TOOL_DEFINITIONS`` schemas
|
|
5
|
+
- Structured ``ToolInvocation`` → ``ToolExecutionResult`` pipeline
|
|
6
|
+
- Plugin-registered tool support via ``REGISTER_TOOL`` hook
|
|
7
|
+
- Safety guardrails (deterministic, no arbitrary code execution)
|
|
8
|
+
|
|
9
|
+
Usage::
|
|
10
|
+
|
|
11
|
+
executor = ToolExecutor(Path("."))
|
|
12
|
+
result = executor.execute(ToolInvocation(tool_name="semantic_search",
|
|
13
|
+
arguments={"query": "parse"}))
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Callable
|
|
21
|
+
|
|
22
|
+
from semantic_code_intelligence.tools import TOOL_DEFINITIONS, ToolRegistry
|
|
23
|
+
from semantic_code_intelligence.tools.protocol import (
|
|
24
|
+
ToolError,
|
|
25
|
+
ToolErrorCode,
|
|
26
|
+
ToolExecutionResult,
|
|
27
|
+
ToolInvocation,
|
|
28
|
+
)
|
|
29
|
+
from semantic_code_intelligence.utils.logging import get_logger
|
|
30
|
+
|
|
31
|
+
logger = get_logger("tools.executor")
|
|
32
|
+
|
|
33
|
+
# Type alias for plugin-registered tool handlers
|
|
34
|
+
ToolHandler = Callable[..., dict[str, Any]]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Allowed tool names (safety guardrail)
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
_BUILTIN_TOOL_NAMES: frozenset[str] = frozenset(
|
|
42
|
+
t["name"] for t in TOOL_DEFINITIONS
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ToolExecutor:
|
|
47
|
+
"""Validates and executes tool invocations against the registry.
|
|
48
|
+
|
|
49
|
+
Adds a structured protocol layer on top of ToolRegistry:
|
|
50
|
+
- Input validation against declared schemas
|
|
51
|
+
- Typed error handling with ToolError / ToolErrorCode
|
|
52
|
+
- Plugin-registered tool dispatch
|
|
53
|
+
- Timing and correlation tracking
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, project_root: Path) -> None:
|
|
57
|
+
self._registry = ToolRegistry(project_root)
|
|
58
|
+
self._plugin_tools: dict[str, dict[str, Any]] = {}
|
|
59
|
+
self._plugin_handlers: dict[str, ToolHandler] = {}
|
|
60
|
+
|
|
61
|
+
# ── tool discovery ────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def available_tools(self) -> list[dict[str, Any]]:
|
|
65
|
+
"""Return schemas of all available tools (built-in + plugin)."""
|
|
66
|
+
tools = list(TOOL_DEFINITIONS)
|
|
67
|
+
for name, schema in self._plugin_tools.items():
|
|
68
|
+
tools.append(schema)
|
|
69
|
+
return tools
|
|
70
|
+
|
|
71
|
+
def list_tool_names(self) -> list[str]:
|
|
72
|
+
"""Return names of all available tools."""
|
|
73
|
+
return [t["name"] for t in self.available_tools]
|
|
74
|
+
|
|
75
|
+
def get_tool_schema(self, tool_name: str) -> dict[str, Any] | None:
|
|
76
|
+
"""Return the schema definition for a specific tool, or None."""
|
|
77
|
+
for t in self.available_tools:
|
|
78
|
+
if t["name"] == tool_name:
|
|
79
|
+
return t
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
# ── plugin tool registration ──────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
def register_plugin_tool(
|
|
85
|
+
self,
|
|
86
|
+
name: str,
|
|
87
|
+
description: str,
|
|
88
|
+
parameters: dict[str, Any],
|
|
89
|
+
handler: ToolHandler,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Register a tool provided by a plugin.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
name: Tool name (must not collide with built-in names).
|
|
95
|
+
description: Human-readable purpose of the tool.
|
|
96
|
+
parameters: Parameter schema matching TOOL_DEFINITIONS format.
|
|
97
|
+
handler: Callable(**kwargs) → dict[str, Any].
|
|
98
|
+
"""
|
|
99
|
+
if name in _BUILTIN_TOOL_NAMES:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"Cannot register plugin tool '{name}': collides with built-in tool"
|
|
102
|
+
)
|
|
103
|
+
self._plugin_tools[name] = {
|
|
104
|
+
"name": name,
|
|
105
|
+
"description": description,
|
|
106
|
+
"parameters": parameters,
|
|
107
|
+
"source": "plugin",
|
|
108
|
+
}
|
|
109
|
+
self._plugin_handlers[name] = handler
|
|
110
|
+
logger.info("Registered plugin tool: %s", name)
|
|
111
|
+
|
|
112
|
+
def unregister_plugin_tool(self, name: str) -> bool:
|
|
113
|
+
"""Remove a plugin-registered tool. Returns True if it existed."""
|
|
114
|
+
removed = name in self._plugin_tools
|
|
115
|
+
self._plugin_tools.pop(name, None)
|
|
116
|
+
self._plugin_handlers.pop(name, None)
|
|
117
|
+
return removed
|
|
118
|
+
|
|
119
|
+
# ── validation ────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
def _validate_arguments(
|
|
122
|
+
self, invocation: ToolInvocation
|
|
123
|
+
) -> ToolError | None:
|
|
124
|
+
"""Validate invocation arguments against the tool schema.
|
|
125
|
+
|
|
126
|
+
Returns a ToolError if validation fails, otherwise None.
|
|
127
|
+
"""
|
|
128
|
+
schema = self.get_tool_schema(invocation.tool_name)
|
|
129
|
+
if schema is None:
|
|
130
|
+
return ToolError(
|
|
131
|
+
tool_name=invocation.tool_name,
|
|
132
|
+
error_code=ToolErrorCode.UNKNOWN_TOOL,
|
|
133
|
+
error_message=f"Unknown tool: {invocation.tool_name}",
|
|
134
|
+
request_id=invocation.request_id,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
params_schema = schema.get("parameters", {})
|
|
138
|
+
for param_name, param_def in params_schema.items():
|
|
139
|
+
if param_def.get("required", False) and param_name not in invocation.arguments:
|
|
140
|
+
return ToolError(
|
|
141
|
+
tool_name=invocation.tool_name,
|
|
142
|
+
error_code=ToolErrorCode.MISSING_REQUIRED_ARG,
|
|
143
|
+
error_message=f"Missing required argument: {param_name}",
|
|
144
|
+
request_id=invocation.request_id,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
# ── execution ─────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
def execute(self, invocation: ToolInvocation) -> ToolExecutionResult:
|
|
152
|
+
"""Execute a tool invocation with full validation and error handling.
|
|
153
|
+
|
|
154
|
+
Pipeline:
|
|
155
|
+
1. Validate tool name and arguments
|
|
156
|
+
2. Route to ToolRegistry (built-in) or plugin handler
|
|
157
|
+
3. Wrap result in ToolExecutionResult with timing
|
|
158
|
+
"""
|
|
159
|
+
# 1. validate
|
|
160
|
+
validation_error = self._validate_arguments(invocation)
|
|
161
|
+
if validation_error is not None:
|
|
162
|
+
return ToolExecutionResult(
|
|
163
|
+
tool_name=invocation.tool_name,
|
|
164
|
+
request_id=invocation.request_id,
|
|
165
|
+
success=False,
|
|
166
|
+
error=validation_error,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# 2. route + execute
|
|
170
|
+
start = time.monotonic()
|
|
171
|
+
try:
|
|
172
|
+
if invocation.tool_name in self._plugin_handlers:
|
|
173
|
+
# Plugin tool
|
|
174
|
+
handler = self._plugin_handlers[invocation.tool_name]
|
|
175
|
+
data = handler(**invocation.arguments)
|
|
176
|
+
success = True
|
|
177
|
+
error = None
|
|
178
|
+
else:
|
|
179
|
+
# Built-in tool via ToolRegistry
|
|
180
|
+
tool_result = self._registry.invoke(
|
|
181
|
+
invocation.tool_name, **invocation.arguments
|
|
182
|
+
)
|
|
183
|
+
data = tool_result.to_dict()
|
|
184
|
+
success = tool_result.success
|
|
185
|
+
error = None
|
|
186
|
+
if not success:
|
|
187
|
+
error = ToolError(
|
|
188
|
+
tool_name=invocation.tool_name,
|
|
189
|
+
error_code=ToolErrorCode.EXECUTION_ERROR,
|
|
190
|
+
error_message=tool_result.error or "Tool execution failed",
|
|
191
|
+
request_id=invocation.request_id,
|
|
192
|
+
)
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
elapsed = (time.monotonic() - start) * 1000
|
|
195
|
+
logger.exception("Tool execution failed: %s", invocation.tool_name)
|
|
196
|
+
return ToolExecutionResult(
|
|
197
|
+
tool_name=invocation.tool_name,
|
|
198
|
+
request_id=invocation.request_id,
|
|
199
|
+
success=False,
|
|
200
|
+
error=ToolError(
|
|
201
|
+
tool_name=invocation.tool_name,
|
|
202
|
+
error_code=ToolErrorCode.EXECUTION_ERROR,
|
|
203
|
+
error_message=str(exc),
|
|
204
|
+
request_id=invocation.request_id,
|
|
205
|
+
),
|
|
206
|
+
execution_time_ms=elapsed,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
elapsed = (time.monotonic() - start) * 1000
|
|
210
|
+
|
|
211
|
+
# 3. wrap result
|
|
212
|
+
return ToolExecutionResult(
|
|
213
|
+
tool_name=invocation.tool_name,
|
|
214
|
+
request_id=invocation.request_id,
|
|
215
|
+
success=success,
|
|
216
|
+
result_payload=data if success else {},
|
|
217
|
+
error=error,
|
|
218
|
+
execution_time_ms=elapsed,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def execute_batch(
|
|
222
|
+
self, invocations: list[ToolInvocation]
|
|
223
|
+
) -> list[ToolExecutionResult]:
|
|
224
|
+
"""Execute multiple tool invocations sequentially."""
|
|
225
|
+
return [self.execute(inv) for inv in invocations]
|
|
226
|
+
|
|
227
|
+
# ── convenience ───────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def registry(self) -> ToolRegistry:
|
|
231
|
+
"""Access the underlying ToolRegistry (for indexing etc.)."""
|
|
232
|
+
return self._registry
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Tool invocation protocol — structured request/response types for AI agent tool use.
|
|
2
|
+
|
|
3
|
+
Defines the data structures that AI coding agents use to invoke CodexA
|
|
4
|
+
tools in a deterministic, type-safe manner:
|
|
5
|
+
|
|
6
|
+
- ``ToolInvocation``: a request to execute a specific tool with arguments
|
|
7
|
+
- ``ToolExecutionResult``: the structured outcome of a tool execution
|
|
8
|
+
- ``ToolError``: a typed error emitted when a tool invocation fails
|
|
9
|
+
|
|
10
|
+
All types are JSON-serializable with ``to_dict()`` / ``from_dict()``
|
|
11
|
+
round-trip support, so agents can reliably parse and construct them.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import time
|
|
18
|
+
import uuid
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Error codes
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
class ToolErrorCode(str, Enum):
|
|
29
|
+
"""Typed error codes for tool failures."""
|
|
30
|
+
|
|
31
|
+
UNKNOWN_TOOL = "unknown_tool"
|
|
32
|
+
INVALID_ARGUMENTS = "invalid_arguments"
|
|
33
|
+
MISSING_REQUIRED_ARG = "missing_required_arg"
|
|
34
|
+
EXECUTION_ERROR = "execution_error"
|
|
35
|
+
TIMEOUT = "timeout"
|
|
36
|
+
PERMISSION_DENIED = "permission_denied"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Tool Invocation (request)
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ToolInvocation:
|
|
45
|
+
"""A request to execute a specific CodexA tool.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
tool_name: Name of the tool to invoke (must match a registered tool).
|
|
49
|
+
arguments: Key-value arguments required by the tool.
|
|
50
|
+
request_id: Caller-assigned correlation ID (auto-generated if empty).
|
|
51
|
+
timestamp: Unix timestamp of the invocation.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
tool_name: str
|
|
55
|
+
arguments: dict[str, Any] = field(default_factory=dict)
|
|
56
|
+
request_id: str = ""
|
|
57
|
+
timestamp: float = 0.0
|
|
58
|
+
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
if not self.request_id:
|
|
61
|
+
self.request_id = uuid.uuid4().hex[:12]
|
|
62
|
+
if self.timestamp == 0.0:
|
|
63
|
+
self.timestamp = time.time()
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict[str, Any]:
|
|
66
|
+
"""Serialise the invocation to a plain dictionary."""
|
|
67
|
+
return {
|
|
68
|
+
"tool_name": self.tool_name,
|
|
69
|
+
"arguments": self.arguments,
|
|
70
|
+
"request_id": self.request_id,
|
|
71
|
+
"timestamp": self.timestamp,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def to_json(self, indent: int | None = None) -> str:
|
|
75
|
+
"""Serialise the invocation to a JSON string."""
|
|
76
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_dict(cls, data: dict[str, Any]) -> ToolInvocation:
|
|
80
|
+
"""Construct a :class:`ToolInvocation` from a dictionary."""
|
|
81
|
+
return cls(
|
|
82
|
+
tool_name=data.get("tool_name", ""),
|
|
83
|
+
arguments=data.get("arguments", {}),
|
|
84
|
+
request_id=data.get("request_id", ""),
|
|
85
|
+
timestamp=data.get("timestamp", 0.0),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_json(cls, text: str) -> ToolInvocation:
|
|
90
|
+
"""Construct a :class:`ToolInvocation` from a JSON string."""
|
|
91
|
+
return cls.from_dict(json.loads(text))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Tool Error
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class ToolError:
|
|
100
|
+
"""A typed error from a failed tool invocation.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
tool_name: The tool that failed.
|
|
104
|
+
error_code: Machine-readable error classification.
|
|
105
|
+
error_message: Human-readable description of what went wrong.
|
|
106
|
+
request_id: Correlation ID from the original invocation.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
tool_name: str
|
|
110
|
+
error_code: str
|
|
111
|
+
error_message: str
|
|
112
|
+
request_id: str = ""
|
|
113
|
+
|
|
114
|
+
def to_dict(self) -> dict[str, Any]:
|
|
115
|
+
"""Serialise the error to a plain dictionary."""
|
|
116
|
+
return {
|
|
117
|
+
"tool_name": self.tool_name,
|
|
118
|
+
"error_code": self.error_code,
|
|
119
|
+
"error_message": self.error_message,
|
|
120
|
+
"request_id": self.request_id,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def to_json(self, indent: int | None = None) -> str:
|
|
124
|
+
"""Serialise the error to a JSON string."""
|
|
125
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def from_dict(cls, data: dict[str, Any]) -> ToolError:
|
|
129
|
+
"""Construct a :class:`ToolError` from a dictionary."""
|
|
130
|
+
return cls(
|
|
131
|
+
tool_name=data.get("tool_name", ""),
|
|
132
|
+
error_code=data.get("error_code", ""),
|
|
133
|
+
error_message=data.get("error_message", ""),
|
|
134
|
+
request_id=data.get("request_id", ""),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
# Tool Execution Result (response)
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class ToolExecutionResult:
|
|
144
|
+
"""Structured outcome of a tool invocation.
|
|
145
|
+
|
|
146
|
+
Attributes:
|
|
147
|
+
tool_name: Name of the tool that was executed.
|
|
148
|
+
request_id: Correlation ID from the original invocation.
|
|
149
|
+
success: Whether the tool completed without error.
|
|
150
|
+
result_payload: Tool output data (only present when success=True).
|
|
151
|
+
error: Typed error (only present when success=False).
|
|
152
|
+
execution_time_ms: Wall-clock execution time in milliseconds.
|
|
153
|
+
timestamp: Unix timestamp of result creation.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
tool_name: str
|
|
157
|
+
request_id: str = ""
|
|
158
|
+
success: bool = True
|
|
159
|
+
result_payload: dict[str, Any] = field(default_factory=dict)
|
|
160
|
+
error: ToolError | None = None
|
|
161
|
+
execution_time_ms: float = 0.0
|
|
162
|
+
timestamp: float = 0.0
|
|
163
|
+
|
|
164
|
+
def __post_init__(self) -> None:
|
|
165
|
+
if self.timestamp == 0.0:
|
|
166
|
+
self.timestamp = time.time()
|
|
167
|
+
|
|
168
|
+
def to_dict(self) -> dict[str, Any]:
|
|
169
|
+
"""Serialise the execution result to a plain dictionary."""
|
|
170
|
+
result: dict[str, Any] = {
|
|
171
|
+
"tool_name": self.tool_name,
|
|
172
|
+
"request_id": self.request_id,
|
|
173
|
+
"success": self.success,
|
|
174
|
+
"execution_time_ms": round(self.execution_time_ms, 2),
|
|
175
|
+
"timestamp": self.timestamp,
|
|
176
|
+
}
|
|
177
|
+
if self.success:
|
|
178
|
+
result["result_payload"] = self.result_payload
|
|
179
|
+
else:
|
|
180
|
+
result["error"] = self.error.to_dict() if self.error else None
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
def to_json(self, indent: int | None = None) -> str:
|
|
184
|
+
"""Serialise the execution result to a JSON string."""
|
|
185
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def from_dict(cls, data: dict[str, Any]) -> ToolExecutionResult:
|
|
189
|
+
"""Construct a :class:`ToolExecutionResult` from a dictionary."""
|
|
190
|
+
error_data = data.get("error")
|
|
191
|
+
error = ToolError.from_dict(error_data) if error_data else None
|
|
192
|
+
return cls(
|
|
193
|
+
tool_name=data.get("tool_name", ""),
|
|
194
|
+
request_id=data.get("request_id", ""),
|
|
195
|
+
success=data.get("success", True),
|
|
196
|
+
result_payload=data.get("result_payload", {}),
|
|
197
|
+
error=error,
|
|
198
|
+
execution_time_ms=data.get("execution_time_ms", 0.0),
|
|
199
|
+
timestamp=data.get("timestamp", 0.0),
|
|
200
|
+
)
|