cortexhub 0.1.7__tar.gz → 0.1.9__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.7 → cortexhub-0.1.9}/PKG-INFO +5 -1
- {cortexhub-0.1.7 → cortexhub-0.1.9}/README.md +3 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/pyproject.toml +2 -1
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/adapters/claude_agents.py +9 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/adapters/langgraph.py +2 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/adapters/openai_agents.py +2 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/client.py +79 -19
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/pipeline.py +8 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/version.py +1 -1
- {cortexhub-0.1.7 → cortexhub-0.1.9}/.gitignore +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/LICENSE +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/adapters/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/adapters/base.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/adapters/crewai.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/audit/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/audit/events.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/auto_protect.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/backend/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/backend/client.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/config.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/context/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/context/enricher.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/errors.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/frameworks.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/guardrails/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/guardrails/injection.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/guardrails/pii.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/guardrails/secrets.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/interceptors/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/interceptors/llm.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/interceptors/mcp.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/policy/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/policy/effects.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/policy/evaluator.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/policy/loader.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/policy/models.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/policy/sync.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/telemetry/__init__.py +0 -0
- {cortexhub-0.1.7 → cortexhub-0.1.9}/src/cortexhub/telemetry/otel.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.9
|
|
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
|
|
@@ -22,6 +22,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
22
22
|
Requires-Python: <3.14,>=3.10
|
|
23
23
|
Requires-Dist: cedarpy>=4.0.0
|
|
24
24
|
Requires-Dist: detect-secrets>=1.5.0
|
|
25
|
+
Requires-Dist: en-core-web-sm>=3.8.0
|
|
25
26
|
Requires-Dist: httpx>=0.28.0
|
|
26
27
|
Requires-Dist: opentelemetry-api>=1.20.0
|
|
27
28
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
|
|
@@ -81,6 +82,9 @@ pip install cortexhub[claude-agents] # Claude Agent SDK
|
|
|
81
82
|
pip install cortexhub[all]
|
|
82
83
|
```
|
|
83
84
|
|
|
85
|
+
Note: The SDK ships with the spaCy `en-core-web-sm` model for Presidio PII detection,
|
|
86
|
+
so it should not download the model at runtime.
|
|
87
|
+
|
|
84
88
|
## Quick Start
|
|
85
89
|
|
|
86
90
|
```python
|
|
@@ -18,6 +18,9 @@ pip install cortexhub[claude-agents] # Claude Agent SDK
|
|
|
18
18
|
pip install cortexhub[all]
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
Note: The SDK ships with the spaCy `en-core-web-sm` model for Presidio PII detection,
|
|
22
|
+
so it should not download the model at runtime.
|
|
23
|
+
|
|
21
24
|
## Quick Start
|
|
22
25
|
|
|
23
26
|
```python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cortexhub"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.9"
|
|
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"
|
|
@@ -37,6 +37,7 @@ dependencies = [
|
|
|
37
37
|
"presidio-analyzer>=2.2.360",
|
|
38
38
|
"presidio-anonymizer>=2.2.360",
|
|
39
39
|
"detect-secrets>=1.5.0",
|
|
40
|
+
"en-core-web-sm>=3.8.0",
|
|
40
41
|
# Utils
|
|
41
42
|
"python-dotenv>=1.0.0",
|
|
42
43
|
# Required by detect-secrets at runtime
|
|
@@ -116,6 +116,14 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
116
116
|
original_handler = original_decorated.handler
|
|
117
117
|
tool_name = original_decorated.name
|
|
118
118
|
tool_description = original_decorated.description
|
|
119
|
+
parameters_schema = None
|
|
120
|
+
if isinstance(input_schema, dict):
|
|
121
|
+
parameters_schema = input_schema
|
|
122
|
+
elif hasattr(input_schema, "model_json_schema"):
|
|
123
|
+
try:
|
|
124
|
+
parameters_schema = input_schema.model_json_schema()
|
|
125
|
+
except Exception:
|
|
126
|
+
parameters_schema = None
|
|
119
127
|
|
|
120
128
|
@wraps(original_handler)
|
|
121
129
|
async def governed_handler(args: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -124,6 +132,7 @@ class ClaudeAgentsAdapter(ToolAdapter):
|
|
|
124
132
|
"name": tool_name,
|
|
125
133
|
"description": tool_description,
|
|
126
134
|
"framework": "claude_agents",
|
|
135
|
+
"parameters_schema": parameters_schema,
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
governed_fn = govern_execution(
|
|
@@ -93,6 +93,7 @@ class LangGraphAdapter(ToolAdapter):
|
|
|
93
93
|
"""Governed tool invocation."""
|
|
94
94
|
tool_name = getattr(self, "name", "unknown_tool")
|
|
95
95
|
tool_description = getattr(self, "description", None)
|
|
96
|
+
parameters_schema = adapter._extract_parameters_schema(self)
|
|
96
97
|
|
|
97
98
|
# Extract args - preserve structure without rewriting
|
|
98
99
|
if isinstance(input, dict):
|
|
@@ -109,6 +110,7 @@ class LangGraphAdapter(ToolAdapter):
|
|
|
109
110
|
"name": tool_name,
|
|
110
111
|
"description": tool_description,
|
|
111
112
|
"framework": "langgraph",
|
|
113
|
+
"parameters_schema": parameters_schema,
|
|
112
114
|
}
|
|
113
115
|
governed_fn = govern_execution(
|
|
114
116
|
tool_fn=lambda *a, **kw: tool_original_invoke(
|
|
@@ -102,6 +102,7 @@ class OpenAIAgentsAdapter(ToolAdapter):
|
|
|
102
102
|
original_invoke = tool.on_invoke_tool
|
|
103
103
|
tool_name = tool.name
|
|
104
104
|
tool_description = tool.description
|
|
105
|
+
parameters_schema = tool.params_json_schema or tool.strict_json_schema
|
|
105
106
|
|
|
106
107
|
async def governed_invoke(ctx, input_json: str) -> Any:
|
|
107
108
|
"""Governed tool invocation."""
|
|
@@ -114,6 +115,7 @@ class OpenAIAgentsAdapter(ToolAdapter):
|
|
|
114
115
|
"name": tool_name,
|
|
115
116
|
"description": tool_description,
|
|
116
117
|
"framework": "openai_agents",
|
|
118
|
+
"parameters_schema": parameters_schema,
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
# Create governed function
|
|
@@ -439,6 +439,7 @@ class CortexHub:
|
|
|
439
439
|
framework: str,
|
|
440
440
|
call_original: Callable[[], Any],
|
|
441
441
|
tool_description: str | None = None,
|
|
442
|
+
parameters_schema: dict[str, Any] | None = None,
|
|
442
443
|
principal: Principal | dict[str, str] | None = None,
|
|
443
444
|
resource_type: str = "Tool",
|
|
444
445
|
) -> Any:
|
|
@@ -475,6 +476,10 @@ class CortexHub:
|
|
|
475
476
|
PolicyViolationError: If policy denies
|
|
476
477
|
ApprovalDeniedError: If approval denied
|
|
477
478
|
"""
|
|
479
|
+
expected_types = self._extract_expected_arg_types(parameters_schema)
|
|
480
|
+
|
|
481
|
+
expected_types = self._extract_expected_arg_types(parameters_schema)
|
|
482
|
+
|
|
478
483
|
# Step 1: Build request (structured, NOT flattened)
|
|
479
484
|
policy_args = self._sanitize_policy_args(args)
|
|
480
485
|
request = self.build_request(
|
|
@@ -515,6 +520,21 @@ class CortexHub:
|
|
|
515
520
|
span.set_attribute("cortexhub.raw.args", json.dumps(args, default=str))
|
|
516
521
|
|
|
517
522
|
try:
|
|
523
|
+
if expected_types and self.enforce and self.evaluator:
|
|
524
|
+
mismatches = self._validate_arg_types(args, expected_types)
|
|
525
|
+
if mismatches:
|
|
526
|
+
message = f"Tool argument type mismatch: {'; '.join(mismatches)}"
|
|
527
|
+
span.add_event(
|
|
528
|
+
"policy.decision",
|
|
529
|
+
attributes={
|
|
530
|
+
"decision.effect": "deny",
|
|
531
|
+
"decision.policy_id": "",
|
|
532
|
+
"decision.reasoning": message,
|
|
533
|
+
"decision.policy_name": "type_mismatch",
|
|
534
|
+
},
|
|
535
|
+
)
|
|
536
|
+
span.set_status(Status(StatusCode.ERROR, message))
|
|
537
|
+
raise PolicyViolationError(message, reasoning=message)
|
|
518
538
|
# Step 4: Policy evaluation (enforcement mode only)
|
|
519
539
|
if self.enforce and self.evaluator:
|
|
520
540
|
if os.getenv("CORTEXHUB_DEBUG_POLICY", "").lower() in ("1", "true", "yes"):
|
|
@@ -636,6 +656,7 @@ class CortexHub:
|
|
|
636
656
|
framework: str,
|
|
637
657
|
call_original: Callable[[], Any],
|
|
638
658
|
tool_description: str | None = None,
|
|
659
|
+
parameters_schema: dict[str, Any] | None = None,
|
|
639
660
|
principal: Principal | dict[str, str] | None = None,
|
|
640
661
|
resource_type: str = "Tool",
|
|
641
662
|
) -> Any:
|
|
@@ -684,6 +705,21 @@ class CortexHub:
|
|
|
684
705
|
span.set_attribute("cortexhub.raw.args", json.dumps(args, default=str))
|
|
685
706
|
|
|
686
707
|
try:
|
|
708
|
+
if expected_types and self.enforce and self.evaluator:
|
|
709
|
+
mismatches = self._validate_arg_types(args, expected_types)
|
|
710
|
+
if mismatches:
|
|
711
|
+
message = f"Tool argument type mismatch: {'; '.join(mismatches)}"
|
|
712
|
+
span.add_event(
|
|
713
|
+
"policy.decision",
|
|
714
|
+
attributes={
|
|
715
|
+
"decision.effect": "deny",
|
|
716
|
+
"decision.policy_id": "",
|
|
717
|
+
"decision.reasoning": message,
|
|
718
|
+
"decision.policy_name": "type_mismatch",
|
|
719
|
+
},
|
|
720
|
+
)
|
|
721
|
+
span.set_status(Status(StatusCode.ERROR, message))
|
|
722
|
+
raise PolicyViolationError(message, reasoning=message)
|
|
687
723
|
# Step 4: Policy evaluation (enforcement mode only)
|
|
688
724
|
if self.enforce and self.evaluator:
|
|
689
725
|
decision = self.evaluator.evaluate(request)
|
|
@@ -1316,6 +1352,49 @@ class CortexHub:
|
|
|
1316
1352
|
return "string"
|
|
1317
1353
|
return "unknown"
|
|
1318
1354
|
|
|
1355
|
+
def _extract_expected_arg_types(self, parameters_schema: dict[str, Any] | None) -> dict[str, str]:
|
|
1356
|
+
if not parameters_schema or not isinstance(parameters_schema, dict):
|
|
1357
|
+
return {}
|
|
1358
|
+
properties = parameters_schema.get("properties")
|
|
1359
|
+
if not isinstance(properties, dict):
|
|
1360
|
+
return {}
|
|
1361
|
+
expected: dict[str, str] = {}
|
|
1362
|
+
for name, schema in properties.items():
|
|
1363
|
+
if not isinstance(schema, dict):
|
|
1364
|
+
continue
|
|
1365
|
+
raw_type = schema.get("type")
|
|
1366
|
+
if isinstance(raw_type, list):
|
|
1367
|
+
raw_type = next((t for t in raw_type if t != "null"), None)
|
|
1368
|
+
if not isinstance(raw_type, str):
|
|
1369
|
+
continue
|
|
1370
|
+
normalized = raw_type.lower()
|
|
1371
|
+
if normalized == "integer":
|
|
1372
|
+
normalized = "number"
|
|
1373
|
+
if normalized in ("number", "string", "boolean"):
|
|
1374
|
+
expected[str(name)] = normalized
|
|
1375
|
+
return expected
|
|
1376
|
+
|
|
1377
|
+
def _validate_arg_types(self, args: dict[str, Any], expected_types: dict[str, str]) -> list[str]:
|
|
1378
|
+
if not isinstance(args, dict):
|
|
1379
|
+
return []
|
|
1380
|
+
mismatches: list[str] = []
|
|
1381
|
+
for name, expected in expected_types.items():
|
|
1382
|
+
if name not in args:
|
|
1383
|
+
continue
|
|
1384
|
+
value = args.get(name)
|
|
1385
|
+
if value is None:
|
|
1386
|
+
continue
|
|
1387
|
+
if expected == "number":
|
|
1388
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
1389
|
+
mismatches.append(f"{name} expected number but got {type(value).__name__}")
|
|
1390
|
+
elif expected == "string":
|
|
1391
|
+
if not isinstance(value, str):
|
|
1392
|
+
mismatches.append(f"{name} expected string but got {type(value).__name__}")
|
|
1393
|
+
elif expected == "boolean":
|
|
1394
|
+
if not isinstance(value, bool):
|
|
1395
|
+
mismatches.append(f"{name} expected boolean but got {type(value).__name__}")
|
|
1396
|
+
return mismatches
|
|
1397
|
+
|
|
1319
1398
|
def trace_llm_call(
|
|
1320
1399
|
self,
|
|
1321
1400
|
model: str,
|
|
@@ -1785,25 +1864,6 @@ class CortexHub:
|
|
|
1785
1864
|
if not isinstance(cleaned_args, dict):
|
|
1786
1865
|
return {}
|
|
1787
1866
|
|
|
1788
|
-
if "amount" in cleaned_args:
|
|
1789
|
-
from decimal import Decimal, InvalidOperation
|
|
1790
|
-
|
|
1791
|
-
amount = cleaned_args.get("amount")
|
|
1792
|
-
dec_amount: Decimal | None = None
|
|
1793
|
-
if isinstance(amount, int):
|
|
1794
|
-
dec_amount = Decimal(amount)
|
|
1795
|
-
elif isinstance(amount, str):
|
|
1796
|
-
try:
|
|
1797
|
-
dec_amount = Decimal(amount)
|
|
1798
|
-
except InvalidOperation:
|
|
1799
|
-
dec_amount = None
|
|
1800
|
-
if dec_amount is not None:
|
|
1801
|
-
quantized = dec_amount.quantize(Decimal("0.01"))
|
|
1802
|
-
if quantized == dec_amount:
|
|
1803
|
-
cleaned_args["amount_cents"] = int(
|
|
1804
|
-
(quantized * 100).to_integral_value()
|
|
1805
|
-
)
|
|
1806
|
-
|
|
1807
1867
|
return cleaned_args
|
|
1808
1868
|
|
|
1809
1869
|
def _summarize_policy_args(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -46,6 +46,12 @@ def govern_execution(
|
|
|
46
46
|
framework = tool_metadata.get("framework", "unknown")
|
|
47
47
|
model = tool_metadata.get("model", "unknown")
|
|
48
48
|
prompt = tool_metadata.get("prompt")
|
|
49
|
+
parameters_schema = (
|
|
50
|
+
tool_metadata.get("parameters_schema")
|
|
51
|
+
or tool_metadata.get("params_json_schema")
|
|
52
|
+
or tool_metadata.get("input_schema")
|
|
53
|
+
or tool_metadata.get("strict_json_schema")
|
|
54
|
+
)
|
|
49
55
|
call_original = tool_metadata.get("call_original")
|
|
50
56
|
if call_original is None:
|
|
51
57
|
call_original = tool_fn
|
|
@@ -78,6 +84,7 @@ def govern_execution(
|
|
|
78
84
|
args=kwargs, # Use kwargs for structured arguments
|
|
79
85
|
framework=framework,
|
|
80
86
|
call_original=lambda: tool_fn(*args, **kwargs),
|
|
87
|
+
parameters_schema=parameters_schema,
|
|
81
88
|
)
|
|
82
89
|
return async_wrapper
|
|
83
90
|
else:
|
|
@@ -88,5 +95,6 @@ def govern_execution(
|
|
|
88
95
|
args=kwargs, # Use kwargs for structured arguments
|
|
89
96
|
framework=framework,
|
|
90
97
|
call_original=lambda: tool_fn(*args, **kwargs),
|
|
98
|
+
parameters_schema=parameters_schema,
|
|
91
99
|
)
|
|
92
100
|
return sync_wrapper
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|