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.
Files changed (189) hide show
  1. codexa-0.4.0.dist-info/METADATA +650 -0
  2. codexa-0.4.0.dist-info/RECORD +189 -0
  3. codexa-0.4.0.dist-info/WHEEL +5 -0
  4. codexa-0.4.0.dist-info/entry_points.txt +2 -0
  5. codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. codexa-0.4.0.dist-info/top_level.txt +1 -0
  7. semantic_code_intelligence/__init__.py +5 -0
  8. semantic_code_intelligence/analysis/__init__.py +21 -0
  9. semantic_code_intelligence/analysis/ai_features.py +351 -0
  10. semantic_code_intelligence/bridge/__init__.py +28 -0
  11. semantic_code_intelligence/bridge/context_provider.py +245 -0
  12. semantic_code_intelligence/bridge/protocol.py +167 -0
  13. semantic_code_intelligence/bridge/server.py +348 -0
  14. semantic_code_intelligence/bridge/vscode.py +271 -0
  15. semantic_code_intelligence/ci/__init__.py +13 -0
  16. semantic_code_intelligence/ci/hooks.py +98 -0
  17. semantic_code_intelligence/ci/hotspots.py +272 -0
  18. semantic_code_intelligence/ci/impact.py +246 -0
  19. semantic_code_intelligence/ci/metrics.py +591 -0
  20. semantic_code_intelligence/ci/pr.py +412 -0
  21. semantic_code_intelligence/ci/quality.py +557 -0
  22. semantic_code_intelligence/ci/templates.py +164 -0
  23. semantic_code_intelligence/ci/trace.py +224 -0
  24. semantic_code_intelligence/cli/__init__.py +0 -0
  25. semantic_code_intelligence/cli/commands/__init__.py +0 -0
  26. semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
  27. semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
  28. semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
  29. semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
  30. semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
  31. semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
  32. semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
  33. semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
  34. semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
  35. semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
  36. semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
  37. semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
  38. semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
  39. semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
  40. semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
  41. semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
  42. semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
  43. semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
  44. semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
  45. semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
  46. semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
  47. semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
  48. semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
  49. semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
  50. semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
  51. semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
  52. semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
  53. semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
  54. semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
  55. semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
  56. semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
  57. semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
  58. semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
  59. semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
  60. semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
  61. semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
  62. semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
  63. semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
  64. semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
  65. semantic_code_intelligence/cli/main.py +65 -0
  66. semantic_code_intelligence/cli/router.py +92 -0
  67. semantic_code_intelligence/config/__init__.py +0 -0
  68. semantic_code_intelligence/config/settings.py +260 -0
  69. semantic_code_intelligence/context/__init__.py +19 -0
  70. semantic_code_intelligence/context/engine.py +429 -0
  71. semantic_code_intelligence/context/memory.py +253 -0
  72. semantic_code_intelligence/daemon/__init__.py +1 -0
  73. semantic_code_intelligence/daemon/watcher.py +515 -0
  74. semantic_code_intelligence/docs/__init__.py +1080 -0
  75. semantic_code_intelligence/embeddings/__init__.py +0 -0
  76. semantic_code_intelligence/embeddings/enhanced.py +131 -0
  77. semantic_code_intelligence/embeddings/generator.py +149 -0
  78. semantic_code_intelligence/embeddings/model_registry.py +100 -0
  79. semantic_code_intelligence/evolution/__init__.py +1 -0
  80. semantic_code_intelligence/evolution/budget_guard.py +111 -0
  81. semantic_code_intelligence/evolution/commit_manager.py +88 -0
  82. semantic_code_intelligence/evolution/context_builder.py +131 -0
  83. semantic_code_intelligence/evolution/engine.py +249 -0
  84. semantic_code_intelligence/evolution/patch_generator.py +229 -0
  85. semantic_code_intelligence/evolution/task_selector.py +214 -0
  86. semantic_code_intelligence/evolution/test_runner.py +111 -0
  87. semantic_code_intelligence/indexing/__init__.py +0 -0
  88. semantic_code_intelligence/indexing/chunker.py +174 -0
  89. semantic_code_intelligence/indexing/parallel.py +86 -0
  90. semantic_code_intelligence/indexing/scanner.py +146 -0
  91. semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
  92. semantic_code_intelligence/llm/__init__.py +62 -0
  93. semantic_code_intelligence/llm/cache.py +219 -0
  94. semantic_code_intelligence/llm/cached_provider.py +145 -0
  95. semantic_code_intelligence/llm/conversation.py +190 -0
  96. semantic_code_intelligence/llm/cross_refactor.py +272 -0
  97. semantic_code_intelligence/llm/investigation.py +274 -0
  98. semantic_code_intelligence/llm/mock_provider.py +77 -0
  99. semantic_code_intelligence/llm/ollama_provider.py +122 -0
  100. semantic_code_intelligence/llm/openai_provider.py +100 -0
  101. semantic_code_intelligence/llm/provider.py +92 -0
  102. semantic_code_intelligence/llm/rate_limiter.py +164 -0
  103. semantic_code_intelligence/llm/reasoning.py +438 -0
  104. semantic_code_intelligence/llm/safety.py +110 -0
  105. semantic_code_intelligence/llm/streaming.py +251 -0
  106. semantic_code_intelligence/lsp/__init__.py +609 -0
  107. semantic_code_intelligence/mcp/__init__.py +393 -0
  108. semantic_code_intelligence/parsing/__init__.py +19 -0
  109. semantic_code_intelligence/parsing/parser.py +375 -0
  110. semantic_code_intelligence/plugins/__init__.py +255 -0
  111. semantic_code_intelligence/plugins/examples/__init__.py +1 -0
  112. semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
  113. semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
  114. semantic_code_intelligence/scalability/__init__.py +205 -0
  115. semantic_code_intelligence/search/__init__.py +0 -0
  116. semantic_code_intelligence/search/formatter.py +123 -0
  117. semantic_code_intelligence/search/grep.py +361 -0
  118. semantic_code_intelligence/search/hybrid_search.py +170 -0
  119. semantic_code_intelligence/search/keyword_search.py +311 -0
  120. semantic_code_intelligence/search/section_expander.py +103 -0
  121. semantic_code_intelligence/services/__init__.py +0 -0
  122. semantic_code_intelligence/services/indexing_service.py +630 -0
  123. semantic_code_intelligence/services/search_service.py +269 -0
  124. semantic_code_intelligence/storage/__init__.py +0 -0
  125. semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
  126. semantic_code_intelligence/storage/hash_store.py +66 -0
  127. semantic_code_intelligence/storage/index_manifest.py +85 -0
  128. semantic_code_intelligence/storage/index_stats.py +138 -0
  129. semantic_code_intelligence/storage/query_history.py +160 -0
  130. semantic_code_intelligence/storage/symbol_registry.py +209 -0
  131. semantic_code_intelligence/storage/vector_store.py +297 -0
  132. semantic_code_intelligence/tests/__init__.py +0 -0
  133. semantic_code_intelligence/tests/test_ai_features.py +351 -0
  134. semantic_code_intelligence/tests/test_chunker.py +119 -0
  135. semantic_code_intelligence/tests/test_cli.py +188 -0
  136. semantic_code_intelligence/tests/test_config.py +154 -0
  137. semantic_code_intelligence/tests/test_context.py +381 -0
  138. semantic_code_intelligence/tests/test_embeddings.py +73 -0
  139. semantic_code_intelligence/tests/test_endtoend.py +1142 -0
  140. semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
  141. semantic_code_intelligence/tests/test_hash_store.py +79 -0
  142. semantic_code_intelligence/tests/test_logging.py +55 -0
  143. semantic_code_intelligence/tests/test_new_cli.py +138 -0
  144. semantic_code_intelligence/tests/test_parser.py +495 -0
  145. semantic_code_intelligence/tests/test_phase10.py +355 -0
  146. semantic_code_intelligence/tests/test_phase11.py +593 -0
  147. semantic_code_intelligence/tests/test_phase12.py +375 -0
  148. semantic_code_intelligence/tests/test_phase13.py +663 -0
  149. semantic_code_intelligence/tests/test_phase14.py +568 -0
  150. semantic_code_intelligence/tests/test_phase15.py +814 -0
  151. semantic_code_intelligence/tests/test_phase16.py +792 -0
  152. semantic_code_intelligence/tests/test_phase17.py +815 -0
  153. semantic_code_intelligence/tests/test_phase18.py +934 -0
  154. semantic_code_intelligence/tests/test_phase19.py +986 -0
  155. semantic_code_intelligence/tests/test_phase20.py +2753 -0
  156. semantic_code_intelligence/tests/test_phase20b.py +2058 -0
  157. semantic_code_intelligence/tests/test_phase20c.py +962 -0
  158. semantic_code_intelligence/tests/test_phase21.py +428 -0
  159. semantic_code_intelligence/tests/test_phase22.py +799 -0
  160. semantic_code_intelligence/tests/test_phase23.py +783 -0
  161. semantic_code_intelligence/tests/test_phase24.py +715 -0
  162. semantic_code_intelligence/tests/test_phase25.py +496 -0
  163. semantic_code_intelligence/tests/test_phase26.py +251 -0
  164. semantic_code_intelligence/tests/test_phase27.py +531 -0
  165. semantic_code_intelligence/tests/test_phase8.py +592 -0
  166. semantic_code_intelligence/tests/test_phase9.py +643 -0
  167. semantic_code_intelligence/tests/test_plugins.py +293 -0
  168. semantic_code_intelligence/tests/test_priority_features.py +727 -0
  169. semantic_code_intelligence/tests/test_router.py +41 -0
  170. semantic_code_intelligence/tests/test_scalability.py +138 -0
  171. semantic_code_intelligence/tests/test_scanner.py +125 -0
  172. semantic_code_intelligence/tests/test_search.py +160 -0
  173. semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
  174. semantic_code_intelligence/tests/test_tools.py +182 -0
  175. semantic_code_intelligence/tests/test_vector_store.py +151 -0
  176. semantic_code_intelligence/tests/test_watcher.py +211 -0
  177. semantic_code_intelligence/tools/__init__.py +442 -0
  178. semantic_code_intelligence/tools/executor.py +232 -0
  179. semantic_code_intelligence/tools/protocol.py +200 -0
  180. semantic_code_intelligence/tui/__init__.py +454 -0
  181. semantic_code_intelligence/utils/__init__.py +0 -0
  182. semantic_code_intelligence/utils/logging.py +112 -0
  183. semantic_code_intelligence/version.py +3 -0
  184. semantic_code_intelligence/web/__init__.py +11 -0
  185. semantic_code_intelligence/web/api.py +289 -0
  186. semantic_code_intelligence/web/server.py +397 -0
  187. semantic_code_intelligence/web/ui.py +659 -0
  188. semantic_code_intelligence/web/visualize.py +226 -0
  189. 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
+ )