cortexhub 0.1.3__tar.gz → 0.1.5__tar.gz
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.
- {cortexhub-0.1.3 → cortexhub-0.1.5}/.gitignore +3 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/PKG-INFO +13 -1
- {cortexhub-0.1.3 → cortexhub-0.1.5}/README.md +10 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/pyproject.toml +3 -1
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/adapters/claude_agents.py +245 -7
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/adapters/crewai.py +275 -1
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/adapters/langgraph.py +119 -0
- cortexhub-0.1.5/src/cortexhub/adapters/openai_agents.py +516 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/client.py +84 -1
- cortexhub-0.1.3/src/cortexhub/adapters/openai_agents.py +0 -192
- {cortexhub-0.1.3 → cortexhub-0.1.5}/LICENSE +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/adapters/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/adapters/base.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/audit/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/audit/events.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/auto_protect.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/backend/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/backend/client.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/config.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/context/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/context/enricher.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/errors.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/frameworks.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/guardrails/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/guardrails/injection.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/guardrails/pii.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/guardrails/secrets.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/interceptors/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/interceptors/llm.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/interceptors/mcp.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/pipeline.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/policy/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/policy/effects.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/policy/evaluator.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/policy/loader.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/policy/models.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/policy/sync.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/telemetry/__init__.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/telemetry/otel.py +0 -0
- {cortexhub-0.1.3 → cortexhub-0.1.5}/src/cortexhub/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cortexhub
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: CortexHub Python SDK - Policy-as-Code for AI Agents
|
|
5
5
|
Project-URL: Homepage, https://cortexhub.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.cortexhub.ai
|
|
@@ -39,12 +39,14 @@ Requires-Dist: crewai>=0.50.0; extra == 'all'
|
|
|
39
39
|
Requires-Dist: langchain-core>=0.2.0; extra == 'all'
|
|
40
40
|
Requires-Dist: langchain-openai>=0.1.0; extra == 'all'
|
|
41
41
|
Requires-Dist: langgraph>=0.2.0; extra == 'all'
|
|
42
|
+
Requires-Dist: litellm>=1.81.5; extra == 'all'
|
|
42
43
|
Requires-Dist: openai-agents>=0.0.3; extra == 'all'
|
|
43
44
|
Provides-Extra: claude-agents
|
|
44
45
|
Requires-Dist: anthropic>=0.40.0; extra == 'claude-agents'
|
|
45
46
|
Requires-Dist: claude-agent-sdk>=0.0.1; extra == 'claude-agents'
|
|
46
47
|
Provides-Extra: crewai
|
|
47
48
|
Requires-Dist: crewai>=0.50.0; extra == 'crewai'
|
|
49
|
+
Requires-Dist: litellm>=1.81.5; extra == 'crewai'
|
|
48
50
|
Provides-Extra: dev
|
|
49
51
|
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
50
52
|
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
@@ -105,6 +107,16 @@ from langgraph.prebuilt import create_react_agent
|
|
|
105
107
|
| OpenAI Agents | `Framework.OPENAI_AGENTS` | `pip install cortexhub[openai-agents]` |
|
|
106
108
|
| Claude Agents | `Framework.CLAUDE_AGENTS` | `pip install cortexhub[claude-agents]` |
|
|
107
109
|
|
|
110
|
+
## Tracing Coverage
|
|
111
|
+
|
|
112
|
+
All frameworks emit `run.started` and `run.completed`/`run.failed` for each run.
|
|
113
|
+
Tool spans (`tool.invoke`) and model spans (`llm.call`) vary by SDK:
|
|
114
|
+
|
|
115
|
+
- **LangGraph**: tool calls via `BaseTool.invoke`, LLM calls via `BaseChatModel.invoke/ainvoke`
|
|
116
|
+
- **CrewAI**: tool calls via `CrewStructuredTool.invoke`/`BaseTool.run`, LLM calls via LiteLLM and `BaseLLM.call/acall`
|
|
117
|
+
- **OpenAI Agents**: tool calls via `function_tool`, LLM calls via `OpenAIResponsesModel` and `OpenAIChatCompletionsModel`
|
|
118
|
+
- **Claude Agents**: tool calls via `@tool` and built-in tool hooks; LLM calls run inside the Claude Code CLI and are not intercepted by the Python SDK
|
|
119
|
+
|
|
108
120
|
## Configuration
|
|
109
121
|
|
|
110
122
|
```bash
|
|
@@ -44,6 +44,16 @@ from langgraph.prebuilt import create_react_agent
|
|
|
44
44
|
| OpenAI Agents | `Framework.OPENAI_AGENTS` | `pip install cortexhub[openai-agents]` |
|
|
45
45
|
| Claude Agents | `Framework.CLAUDE_AGENTS` | `pip install cortexhub[claude-agents]` |
|
|
46
46
|
|
|
47
|
+
## Tracing Coverage
|
|
48
|
+
|
|
49
|
+
All frameworks emit `run.started` and `run.completed`/`run.failed` for each run.
|
|
50
|
+
Tool spans (`tool.invoke`) and model spans (`llm.call`) vary by SDK:
|
|
51
|
+
|
|
52
|
+
- **LangGraph**: tool calls via `BaseTool.invoke`, LLM calls via `BaseChatModel.invoke/ainvoke`
|
|
53
|
+
- **CrewAI**: tool calls via `CrewStructuredTool.invoke`/`BaseTool.run`, LLM calls via LiteLLM and `BaseLLM.call/acall`
|
|
54
|
+
- **OpenAI Agents**: tool calls via `function_tool`, LLM calls via `OpenAIResponsesModel` and `OpenAIChatCompletionsModel`
|
|
55
|
+
- **Claude Agents**: tool calls via `@tool` and built-in tool hooks; LLM calls run inside the Claude Code CLI and are not intercepted by the Python SDK
|
|
56
|
+
|
|
47
57
|
## Configuration
|
|
48
58
|
|
|
49
59
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cortexhub"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.5"
|
|
4
4
|
description = "CortexHub Python SDK - Policy-as-Code for AI Agents"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10,<3.14"
|
|
@@ -55,6 +55,7 @@ langgraph = [
|
|
|
55
55
|
|
|
56
56
|
crewai = [
|
|
57
57
|
"crewai>=0.50.0",
|
|
58
|
+
"litellm>=1.81.5",
|
|
58
59
|
]
|
|
59
60
|
|
|
60
61
|
openai-agents = [
|
|
@@ -139,6 +140,7 @@ dev = [
|
|
|
139
140
|
"langchain-openai>=0.1.0",
|
|
140
141
|
"langgraph>=0.2.0",
|
|
141
142
|
"crewai>=0.50.0",
|
|
143
|
+
"litellm>=1.81.5",
|
|
142
144
|
"openai-agents>=0.0.3",
|
|
143
145
|
"anthropic>=0.40.0",
|
|
144
146
|
"claude-agent-sdk>=0.0.1",
|
|
@@ -19,11 +19,13 @@ Architectural rules:
|
|
|
19
19
|
- No governance logic in adapter
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
import json
|
|
22
23
|
import os
|
|
23
24
|
from functools import wraps
|
|
24
25
|
from typing import TYPE_CHECKING, Any, Callable, Awaitable
|
|
25
26
|
|
|
26
27
|
import structlog
|
|
28
|
+
from opentelemetry.trace import SpanKind, Status, StatusCode
|
|
27
29
|
|
|
28
30
|
from cortexhub.adapters.base import ToolAdapter
|
|
29
31
|
from cortexhub.pipeline import govern_execution
|
|
@@ -36,6 +38,10 @@ logger = structlog.get_logger(__name__)
|
|
|
36
38
|
# Attribute names for storing originals
|
|
37
39
|
_ORIGINAL_TOOL_ATTR = "__cortexhub_original_tool__"
|
|
38
40
|
_PATCHED_ATTR = "__cortexhub_patched__"
|
|
41
|
+
_ORIGINAL_QUERY_ATTR = "__cortexhub_original_query__"
|
|
42
|
+
_ORIGINAL_RECEIVE_RESPONSE_ATTR = "__cortexhub_original_receive_response__"
|
|
43
|
+
_ORIGINAL_CLIENT_QUERY_ATTR = "__cortexhub_original_client_query__"
|
|
44
|
+
_PATCHED_RUN_ATTR = "__cortexhub_run_patched__"
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
class ClaudeAgentsAdapter(ToolAdapter):
|
|
@@ -64,6 +70,11 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
64
70
|
def framework_name(self) -> str:
|
|
65
71
|
return "claude_agents"
|
|
66
72
|
|
|
73
|
+
def __init__(self, cortex_hub: Any):
|
|
74
|
+
super().__init__(cortex_hub)
|
|
75
|
+
self._hook_spans: dict[str, Any] = {}
|
|
76
|
+
self._active_run_session_id: str | None = None
|
|
77
|
+
|
|
67
78
|
def _get_framework_modules(self) -> list[str]:
|
|
68
79
|
return ["claude_agent_sdk"]
|
|
69
80
|
|
|
@@ -145,6 +156,8 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
145
156
|
setattr(claude_agent_sdk, _PATCHED_ATTR, True)
|
|
146
157
|
|
|
147
158
|
logger.info("Claude Agent SDK @tool decorator patched successfully")
|
|
159
|
+
|
|
160
|
+
self._patch_run_completion(cortex_hub)
|
|
148
161
|
|
|
149
162
|
except ImportError:
|
|
150
163
|
logger.debug("Claude Agent SDK not installed, skipping adapter")
|
|
@@ -161,6 +174,23 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
161
174
|
claude_agent_sdk.tool = original
|
|
162
175
|
setattr(claude_agent_sdk, _PATCHED_ATTR, False)
|
|
163
176
|
logger.info("Claude Agent SDK adapter unpatched")
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
from claude_agent_sdk import ClaudeSDKClient
|
|
180
|
+
|
|
181
|
+
if hasattr(claude_agent_sdk, _ORIGINAL_QUERY_ATTR):
|
|
182
|
+
claude_agent_sdk.query = getattr(claude_agent_sdk, _ORIGINAL_QUERY_ATTR)
|
|
183
|
+
if hasattr(ClaudeSDKClient, _ORIGINAL_CLIENT_QUERY_ATTR):
|
|
184
|
+
ClaudeSDKClient.query = getattr(
|
|
185
|
+
ClaudeSDKClient, _ORIGINAL_CLIENT_QUERY_ATTR
|
|
186
|
+
)
|
|
187
|
+
if hasattr(ClaudeSDKClient, _ORIGINAL_RECEIVE_RESPONSE_ATTR):
|
|
188
|
+
ClaudeSDKClient.receive_response = getattr(
|
|
189
|
+
ClaudeSDKClient, _ORIGINAL_RECEIVE_RESPONSE_ATTR
|
|
190
|
+
)
|
|
191
|
+
setattr(claude_agent_sdk, _PATCHED_RUN_ATTR, False)
|
|
192
|
+
except ImportError:
|
|
193
|
+
pass
|
|
164
194
|
except ImportError:
|
|
165
195
|
pass
|
|
166
196
|
|
|
@@ -171,6 +201,94 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
171
201
|
def _discover_tools(self) -> list[dict[str, Any]]:
|
|
172
202
|
"""Discover tools from Claude Agent SDK (best-effort)."""
|
|
173
203
|
return []
|
|
204
|
+
|
|
205
|
+
def _patch_run_completion(self, cortex_hub) -> None:
|
|
206
|
+
"""Patch Claude Agent SDK runs to emit run completion."""
|
|
207
|
+
try:
|
|
208
|
+
import claude_agent_sdk
|
|
209
|
+
from claude_agent_sdk import ClaudeSDKClient, ResultMessage
|
|
210
|
+
adapter = self
|
|
211
|
+
|
|
212
|
+
if getattr(claude_agent_sdk, _PATCHED_RUN_ATTR, False):
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
if not hasattr(claude_agent_sdk, _ORIGINAL_QUERY_ATTR):
|
|
216
|
+
setattr(claude_agent_sdk, _ORIGINAL_QUERY_ATTR, claude_agent_sdk.query)
|
|
217
|
+
original_query = getattr(claude_agent_sdk, _ORIGINAL_QUERY_ATTR)
|
|
218
|
+
|
|
219
|
+
async def patched_query(*args, **kwargs):
|
|
220
|
+
status = None
|
|
221
|
+
failed = False
|
|
222
|
+
cortex_hub.start_run(framework="claude_agents")
|
|
223
|
+
adapter._active_run_session_id = cortex_hub.session_id
|
|
224
|
+
try:
|
|
225
|
+
async for message in original_query(*args, **kwargs):
|
|
226
|
+
if isinstance(message, ResultMessage):
|
|
227
|
+
status = "failed" if message.is_error else "completed"
|
|
228
|
+
yield message
|
|
229
|
+
except Exception:
|
|
230
|
+
failed = True
|
|
231
|
+
raise
|
|
232
|
+
finally:
|
|
233
|
+
if status is None and failed:
|
|
234
|
+
status = "failed"
|
|
235
|
+
if status:
|
|
236
|
+
cortex_hub.finish_run(framework="claude_agents", status=status)
|
|
237
|
+
adapter._active_run_session_id = None
|
|
238
|
+
|
|
239
|
+
claude_agent_sdk.query = patched_query
|
|
240
|
+
|
|
241
|
+
if not hasattr(ClaudeSDKClient, _ORIGINAL_RECEIVE_RESPONSE_ATTR):
|
|
242
|
+
setattr(
|
|
243
|
+
ClaudeSDKClient,
|
|
244
|
+
_ORIGINAL_RECEIVE_RESPONSE_ATTR,
|
|
245
|
+
ClaudeSDKClient.receive_response,
|
|
246
|
+
)
|
|
247
|
+
original_receive_response = getattr(ClaudeSDKClient, _ORIGINAL_RECEIVE_RESPONSE_ATTR)
|
|
248
|
+
if not hasattr(ClaudeSDKClient, _ORIGINAL_CLIENT_QUERY_ATTR):
|
|
249
|
+
setattr(
|
|
250
|
+
ClaudeSDKClient,
|
|
251
|
+
_ORIGINAL_CLIENT_QUERY_ATTR,
|
|
252
|
+
ClaudeSDKClient.query,
|
|
253
|
+
)
|
|
254
|
+
original_client_query = getattr(ClaudeSDKClient, _ORIGINAL_CLIENT_QUERY_ATTR)
|
|
255
|
+
|
|
256
|
+
async def patched_client_query(self, *args, **kwargs):
|
|
257
|
+
cortex_hub.start_run(framework="claude_agents")
|
|
258
|
+
adapter._active_run_session_id = cortex_hub.session_id
|
|
259
|
+
try:
|
|
260
|
+
return await original_client_query(self, *args, **kwargs)
|
|
261
|
+
except Exception:
|
|
262
|
+
cortex_hub.finish_run(framework="claude_agents", status="failed")
|
|
263
|
+
adapter._active_run_session_id = None
|
|
264
|
+
raise
|
|
265
|
+
|
|
266
|
+
async def patched_receive_response(self, *args, **kwargs):
|
|
267
|
+
status = None
|
|
268
|
+
failed = False
|
|
269
|
+
try:
|
|
270
|
+
async for message in original_receive_response(self, *args, **kwargs):
|
|
271
|
+
if isinstance(message, ResultMessage):
|
|
272
|
+
status = "failed" if message.is_error else "completed"
|
|
273
|
+
yield message
|
|
274
|
+
except Exception:
|
|
275
|
+
failed = True
|
|
276
|
+
raise
|
|
277
|
+
finally:
|
|
278
|
+
if status is None and failed:
|
|
279
|
+
status = "failed"
|
|
280
|
+
if status:
|
|
281
|
+
cortex_hub.finish_run(framework="claude_agents", status=status)
|
|
282
|
+
adapter._active_run_session_id = None
|
|
283
|
+
|
|
284
|
+
ClaudeSDKClient.query = patched_client_query
|
|
285
|
+
ClaudeSDKClient.receive_response = patched_receive_response
|
|
286
|
+
setattr(claude_agent_sdk, _PATCHED_RUN_ATTR, True)
|
|
287
|
+
logger.info("Claude Agent SDK run completion patched successfully")
|
|
288
|
+
except ImportError:
|
|
289
|
+
logger.debug("Claude Agent SDK run completion patch skipped")
|
|
290
|
+
except Exception as e:
|
|
291
|
+
logger.debug("Claude Agent SDK run completion patch failed", reason=str(e))
|
|
174
292
|
|
|
175
293
|
def create_governance_hooks(self) -> dict[str, list]:
|
|
176
294
|
"""Create CortexHub governance hooks for Claude Agent SDK.
|
|
@@ -191,6 +309,65 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
191
309
|
)
|
|
192
310
|
"""
|
|
193
311
|
cortex_hub = self.cortex_hub
|
|
312
|
+
span_store = self._hook_spans
|
|
313
|
+
adapter = self
|
|
314
|
+
|
|
315
|
+
def _current_session_id() -> str:
|
|
316
|
+
return adapter._active_run_session_id or cortex_hub.session_id
|
|
317
|
+
|
|
318
|
+
def _start_tool_span(
|
|
319
|
+
*,
|
|
320
|
+
tool_name: str,
|
|
321
|
+
tool_description: str,
|
|
322
|
+
policy_args: dict[str, Any],
|
|
323
|
+
raw_args: dict[str, Any],
|
|
324
|
+
tool_use_id: str | None,
|
|
325
|
+
):
|
|
326
|
+
span = cortex_hub._tracer.start_span(
|
|
327
|
+
name="tool.invoke",
|
|
328
|
+
kind=SpanKind.INTERNAL,
|
|
329
|
+
)
|
|
330
|
+
span.set_attribute("cortexhub.session.id", _current_session_id())
|
|
331
|
+
span.set_attribute("cortexhub.agent.id", cortex_hub.agent_id)
|
|
332
|
+
span.set_attribute("cortexhub.tool.name", tool_name)
|
|
333
|
+
span.set_attribute("cortexhub.tool.framework", "claude_agents")
|
|
334
|
+
span.set_attribute("cortexhub.tool.description", tool_description)
|
|
335
|
+
|
|
336
|
+
if tool_use_id:
|
|
337
|
+
span.set_attribute("cortexhub.tool.use_id", tool_use_id)
|
|
338
|
+
|
|
339
|
+
if policy_args:
|
|
340
|
+
arg_names = list(policy_args.keys())
|
|
341
|
+
if arg_names:
|
|
342
|
+
span.set_attribute("cortexhub.tool.arg_names", arg_names)
|
|
343
|
+
arg_schema = cortex_hub._infer_arg_schema(policy_args)
|
|
344
|
+
if arg_schema:
|
|
345
|
+
span.set_attribute(
|
|
346
|
+
"cortexhub.tool.arg_schema",
|
|
347
|
+
json.dumps(arg_schema),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
if not cortex_hub.privacy and raw_args:
|
|
351
|
+
span.set_attribute("cortexhub.raw.args", json.dumps(raw_args, default=str))
|
|
352
|
+
|
|
353
|
+
return span
|
|
354
|
+
|
|
355
|
+
def _finish_tool_span(
|
|
356
|
+
span,
|
|
357
|
+
*,
|
|
358
|
+
success: bool,
|
|
359
|
+
error_message: str | None = None,
|
|
360
|
+
result: Any | None = None,
|
|
361
|
+
) -> None:
|
|
362
|
+
span.set_attribute("cortexhub.result.success", success)
|
|
363
|
+
if error_message:
|
|
364
|
+
span.set_attribute("cortexhub.error.message", error_message)
|
|
365
|
+
span.set_status(Status(StatusCode.ERROR, error_message))
|
|
366
|
+
else:
|
|
367
|
+
span.set_status(Status(StatusCode.OK))
|
|
368
|
+
if result is not None and not cortex_hub.privacy:
|
|
369
|
+
span.set_attribute("cortexhub.raw.result", json.dumps(result, default=str))
|
|
370
|
+
span.end()
|
|
194
371
|
|
|
195
372
|
async def pre_tool_governance(
|
|
196
373
|
input_data: dict[str, Any],
|
|
@@ -204,12 +381,18 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
204
381
|
"""
|
|
205
382
|
tool_name = input_data.get("tool_name", "unknown")
|
|
206
383
|
tool_input = input_data.get("tool_input", {})
|
|
384
|
+
if not isinstance(tool_input, dict):
|
|
385
|
+
tool_input = {"_raw": tool_input}
|
|
386
|
+
policy_args = cortex_hub._sanitize_policy_args(tool_input)
|
|
207
387
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
388
|
+
tool_description = f"Claude Agent SDK built-in tool: {tool_name}"
|
|
389
|
+
span = _start_tool_span(
|
|
390
|
+
tool_name=tool_name,
|
|
391
|
+
tool_description=tool_description,
|
|
392
|
+
policy_args=policy_args,
|
|
393
|
+
raw_args=tool_input,
|
|
394
|
+
tool_use_id=tool_use_id,
|
|
395
|
+
)
|
|
213
396
|
|
|
214
397
|
# Build authorization request and evaluate
|
|
215
398
|
from cortexhub.policy.models import (
|
|
@@ -222,7 +405,7 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
222
405
|
principal=Principal(type="Agent", id=cortex_hub.agent_id),
|
|
223
406
|
action=Action(type="tool.invoke", name=tool_name),
|
|
224
407
|
resource=PolicyResource(type="Tool", id=tool_name),
|
|
225
|
-
args=
|
|
408
|
+
args=policy_args,
|
|
226
409
|
framework="claude_agents",
|
|
227
410
|
)
|
|
228
411
|
|
|
@@ -230,8 +413,23 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
230
413
|
if cortex_hub.enforce and cortex_hub.evaluator:
|
|
231
414
|
from cortexhub.policy.effects import Effect
|
|
232
415
|
decision = cortex_hub.evaluator.evaluate(request)
|
|
416
|
+
|
|
417
|
+
span.add_event(
|
|
418
|
+
"policy.decision",
|
|
419
|
+
attributes={
|
|
420
|
+
"decision.effect": decision.effect.value,
|
|
421
|
+
"decision.policy_id": decision.policy_id or "",
|
|
422
|
+
"decision.reasoning": decision.reasoning,
|
|
423
|
+
"decision.policy_name": decision.policy_name or "",
|
|
424
|
+
},
|
|
425
|
+
)
|
|
233
426
|
|
|
234
427
|
if decision.effect == Effect.DENY:
|
|
428
|
+
_finish_tool_span(
|
|
429
|
+
span,
|
|
430
|
+
success=False,
|
|
431
|
+
error_message=decision.reasoning,
|
|
432
|
+
)
|
|
235
433
|
return {
|
|
236
434
|
"hookSpecificOutput": {
|
|
237
435
|
"hookEventName": "PreToolUse",
|
|
@@ -242,7 +440,7 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
242
440
|
|
|
243
441
|
if decision.effect == Effect.ESCALATE:
|
|
244
442
|
try:
|
|
245
|
-
context_hash = cortex_hub._compute_context_hash(tool_name,
|
|
443
|
+
context_hash = cortex_hub._compute_context_hash(tool_name, policy_args)
|
|
246
444
|
approval_response = cortex_hub.backend.create_approval(
|
|
247
445
|
run_id=cortex_hub.session_id,
|
|
248
446
|
trace_id=cortex_hub._get_current_trace_id(),
|
|
@@ -260,6 +458,11 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
260
458
|
approval_id = approval_response.get("approval_id", "unknown")
|
|
261
459
|
except Exception as e:
|
|
262
460
|
logger.error("Failed to create approval", error=str(e))
|
|
461
|
+
_finish_tool_span(
|
|
462
|
+
span,
|
|
463
|
+
success=False,
|
|
464
|
+
error_message=str(e),
|
|
465
|
+
)
|
|
263
466
|
return {
|
|
264
467
|
"hookSpecificOutput": {
|
|
265
468
|
"hookEventName": "PreToolUse",
|
|
@@ -270,6 +473,20 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
270
473
|
}
|
|
271
474
|
}
|
|
272
475
|
|
|
476
|
+
span.add_event(
|
|
477
|
+
"approval.created",
|
|
478
|
+
attributes={
|
|
479
|
+
"approval_id": approval_id,
|
|
480
|
+
"tool_name": tool_name,
|
|
481
|
+
"policy_id": decision.policy_id or "",
|
|
482
|
+
"expires_at": approval_response.get("expires_at", ""),
|
|
483
|
+
},
|
|
484
|
+
)
|
|
485
|
+
_finish_tool_span(
|
|
486
|
+
span,
|
|
487
|
+
success=False,
|
|
488
|
+
error_message="Approval required",
|
|
489
|
+
)
|
|
273
490
|
return {
|
|
274
491
|
"hookSpecificOutput": {
|
|
275
492
|
"hookEventName": "PreToolUse",
|
|
@@ -281,6 +498,10 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
281
498
|
}
|
|
282
499
|
|
|
283
500
|
# Allow execution
|
|
501
|
+
if tool_use_id:
|
|
502
|
+
span_store[tool_use_id] = span
|
|
503
|
+
else:
|
|
504
|
+
span.end()
|
|
284
505
|
return {}
|
|
285
506
|
|
|
286
507
|
async def post_tool_governance(
|
|
@@ -294,6 +515,21 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
294
515
|
"""
|
|
295
516
|
tool_name = input_data.get("tool_name", "unknown")
|
|
296
517
|
tool_response = input_data.get("tool_response", {})
|
|
518
|
+
tool_input = input_data.get("tool_input", {})
|
|
519
|
+
if not isinstance(tool_input, dict):
|
|
520
|
+
tool_input = {"_raw": tool_input}
|
|
521
|
+
policy_args = cortex_hub._sanitize_policy_args(tool_input)
|
|
522
|
+
|
|
523
|
+
span = span_store.pop(tool_use_id, None) if tool_use_id else None
|
|
524
|
+
if span is None:
|
|
525
|
+
tool_description = f"Claude Agent SDK built-in tool: {tool_name}"
|
|
526
|
+
span = _start_tool_span(
|
|
527
|
+
tool_name=tool_name,
|
|
528
|
+
tool_description=tool_description,
|
|
529
|
+
policy_args=policy_args,
|
|
530
|
+
raw_args=tool_input,
|
|
531
|
+
tool_use_id=tool_use_id,
|
|
532
|
+
)
|
|
297
533
|
|
|
298
534
|
# Log the tool execution
|
|
299
535
|
logger.debug(
|
|
@@ -301,6 +537,8 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
301
537
|
tool=tool_name,
|
|
302
538
|
framework="claude_agents",
|
|
303
539
|
)
|
|
540
|
+
|
|
541
|
+
_finish_tool_span(span, success=True, result=tool_response)
|
|
304
542
|
|
|
305
543
|
return {}
|
|
306
544
|
|