agent_os_kernel 3.1.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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
OpenAI Client Adapter - Drop-In Middleware for Agent Control Plane
|
|
5
|
+
|
|
6
|
+
This adapter wraps the OpenAI client to automatically intercept and govern
|
|
7
|
+
tool calls made by LLMs. It provides "zero-friction" integration - developers
|
|
8
|
+
can continue using the standard OpenAI SDK while benefiting from the control
|
|
9
|
+
plane's governance and safety features.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from openai import OpenAI
|
|
13
|
+
from agent_control_plane import AgentControlPlane
|
|
14
|
+
from agent_control_plane.adapter import ControlPlaneAdapter
|
|
15
|
+
|
|
16
|
+
# Standard setup
|
|
17
|
+
client = OpenAI(api_key="your-key")
|
|
18
|
+
control_plane = AgentControlPlane()
|
|
19
|
+
agent_context = control_plane.create_agent("my-agent", permissions)
|
|
20
|
+
|
|
21
|
+
# Wrap with adapter
|
|
22
|
+
governed_client = ControlPlaneAdapter(
|
|
23
|
+
control_plane=control_plane,
|
|
24
|
+
agent_context=agent_context,
|
|
25
|
+
original_client=client
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Use exactly as you would use OpenAI client
|
|
29
|
+
response = governed_client.chat.completions.create(
|
|
30
|
+
model="gpt-4",
|
|
31
|
+
messages=[...],
|
|
32
|
+
tools=[...]
|
|
33
|
+
)
|
|
34
|
+
# Tool calls are automatically governed by the control plane!
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from typing import Any, Dict, List, Optional, Callable
|
|
38
|
+
import json
|
|
39
|
+
import logging
|
|
40
|
+
from datetime import datetime
|
|
41
|
+
|
|
42
|
+
from .agent_kernel import ActionType, AgentContext
|
|
43
|
+
from .control_plane import AgentControlPlane
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Mapping from common OpenAI tool names to ActionType
|
|
47
|
+
DEFAULT_TOOL_MAPPING = {
|
|
48
|
+
# File operations
|
|
49
|
+
"read_file": ActionType.FILE_READ,
|
|
50
|
+
"write_file": ActionType.FILE_WRITE,
|
|
51
|
+
"file_read": ActionType.FILE_READ,
|
|
52
|
+
"file_write": ActionType.FILE_WRITE,
|
|
53
|
+
|
|
54
|
+
# Code execution
|
|
55
|
+
"execute_code": ActionType.CODE_EXECUTION,
|
|
56
|
+
"run_code": ActionType.CODE_EXECUTION,
|
|
57
|
+
"python": ActionType.CODE_EXECUTION,
|
|
58
|
+
"bash": ActionType.CODE_EXECUTION,
|
|
59
|
+
"code_interpreter": ActionType.CODE_EXECUTION,
|
|
60
|
+
|
|
61
|
+
# Database operations
|
|
62
|
+
"database_query": ActionType.DATABASE_QUERY,
|
|
63
|
+
"sql_query": ActionType.DATABASE_QUERY,
|
|
64
|
+
"db_query": ActionType.DATABASE_QUERY,
|
|
65
|
+
"database_write": ActionType.DATABASE_WRITE,
|
|
66
|
+
"sql_write": ActionType.DATABASE_WRITE,
|
|
67
|
+
"db_write": ActionType.DATABASE_WRITE,
|
|
68
|
+
|
|
69
|
+
# API calls
|
|
70
|
+
"api_call": ActionType.API_CALL,
|
|
71
|
+
"http_request": ActionType.API_CALL,
|
|
72
|
+
"make_request": ActionType.API_CALL,
|
|
73
|
+
|
|
74
|
+
# Workflow operations
|
|
75
|
+
"trigger_workflow": ActionType.WORKFLOW_TRIGGER,
|
|
76
|
+
"start_workflow": ActionType.WORKFLOW_TRIGGER,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ChatCompletionsWrapper:
|
|
81
|
+
"""
|
|
82
|
+
Wrapper for chat.completions that intercepts tool calls.
|
|
83
|
+
|
|
84
|
+
This class mimics the OpenAI client's chat.completions interface
|
|
85
|
+
while adding governance checks for tool calls.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
original_completions: Any,
|
|
91
|
+
control_plane: AgentControlPlane,
|
|
92
|
+
agent_context: AgentContext,
|
|
93
|
+
tool_mapping: Dict[str, ActionType],
|
|
94
|
+
logger: logging.Logger,
|
|
95
|
+
on_block: Optional[Callable] = None
|
|
96
|
+
):
|
|
97
|
+
self.original = original_completions
|
|
98
|
+
self.control_plane = control_plane
|
|
99
|
+
self.agent_context = agent_context
|
|
100
|
+
self.tool_mapping = tool_mapping
|
|
101
|
+
self.logger = logger
|
|
102
|
+
self.on_block = on_block
|
|
103
|
+
|
|
104
|
+
def create(self, **kwargs) -> Any:
|
|
105
|
+
"""
|
|
106
|
+
Create a chat completion with automatic tool call governance.
|
|
107
|
+
|
|
108
|
+
This method:
|
|
109
|
+
1. Calls the OpenAI API to get the LLM's response
|
|
110
|
+
2. Intercepts any tool_calls in the response
|
|
111
|
+
3. Checks each tool call against the control plane
|
|
112
|
+
4. Blocks or modifies tool calls that violate policies
|
|
113
|
+
5. Returns the (possibly modified) response
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
**kwargs: All standard OpenAI chat.completions.create parameters
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The OpenAI ChatCompletion response, with tool calls governed
|
|
120
|
+
"""
|
|
121
|
+
# 1. Let the LLM think - call the original OpenAI API
|
|
122
|
+
self.logger.debug(f"Agent {self.agent_context.agent_id}: Calling OpenAI API")
|
|
123
|
+
response = self.original.create(**kwargs)
|
|
124
|
+
|
|
125
|
+
# 2. Check if there are tool calls to intercept
|
|
126
|
+
if not hasattr(response, 'choices') or not response.choices:
|
|
127
|
+
return response
|
|
128
|
+
|
|
129
|
+
choice = response.choices[0]
|
|
130
|
+
if not hasattr(choice, 'message') or not hasattr(choice.message, 'tool_calls'):
|
|
131
|
+
return response
|
|
132
|
+
|
|
133
|
+
if not choice.message.tool_calls:
|
|
134
|
+
return response
|
|
135
|
+
|
|
136
|
+
# 3. Intercept and govern each tool call
|
|
137
|
+
self.logger.info(
|
|
138
|
+
f"Agent {self.agent_context.agent_id}: Intercepting {len(choice.message.tool_calls)} tool call(s)"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for tool_call in choice.message.tool_calls:
|
|
142
|
+
if not hasattr(tool_call, 'function'):
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
tool_name = tool_call.function.name
|
|
146
|
+
|
|
147
|
+
# Parse arguments (they come as JSON string from OpenAI)
|
|
148
|
+
try:
|
|
149
|
+
tool_args = json.loads(tool_call.function.arguments) if tool_call.function.arguments else {}
|
|
150
|
+
except json.JSONDecodeError as e:
|
|
151
|
+
self.logger.warning(
|
|
152
|
+
f"Could not parse arguments for tool '{tool_name}': {tool_call.function.arguments}. "
|
|
153
|
+
f"Error: {e}. Using empty dict."
|
|
154
|
+
)
|
|
155
|
+
tool_args = {}
|
|
156
|
+
|
|
157
|
+
# Map tool name to ActionType
|
|
158
|
+
action_type = self._map_tool_to_action(tool_name)
|
|
159
|
+
|
|
160
|
+
if action_type is None:
|
|
161
|
+
self.logger.warning(f"Unknown tool '{tool_name}', allowing by default")
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# THE KERNEL CHECK - This is where governance happens
|
|
165
|
+
self.logger.debug(f"Checking permission for {tool_name} -> {action_type.value}")
|
|
166
|
+
|
|
167
|
+
# Check permission through control plane
|
|
168
|
+
check_result = self.control_plane.execute_action(
|
|
169
|
+
self.agent_context,
|
|
170
|
+
action_type,
|
|
171
|
+
tool_args
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if not check_result['success']:
|
|
175
|
+
# Action is BLOCKED by the control plane
|
|
176
|
+
self.logger.warning(
|
|
177
|
+
f"BLOCKED: Agent {self.agent_context.agent_id} attempted {tool_name} "
|
|
178
|
+
f"but was denied: {check_result.get('error', 'Unknown reason')}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Overwrite the tool call with a "blocked" indicator
|
|
182
|
+
# The Mute Agent pattern: return NULL/minimal response
|
|
183
|
+
tool_call.function.name = "blocked_action"
|
|
184
|
+
tool_call.function.arguments = json.dumps({
|
|
185
|
+
"original_tool": tool_name,
|
|
186
|
+
"reason": "Action blocked by Agent Control Plane",
|
|
187
|
+
"error": check_result.get('error', 'Policy violation')
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
# Call the optional callback
|
|
191
|
+
if self.on_block:
|
|
192
|
+
self.on_block(tool_name, tool_args, check_result)
|
|
193
|
+
else:
|
|
194
|
+
self.logger.info(f"ALLOWED: {tool_name} for agent {self.agent_context.agent_id}")
|
|
195
|
+
|
|
196
|
+
return response
|
|
197
|
+
|
|
198
|
+
def _map_tool_to_action(self, tool_name: str) -> Optional[ActionType]:
|
|
199
|
+
"""
|
|
200
|
+
Map an OpenAI tool name to an ActionType.
|
|
201
|
+
|
|
202
|
+
This uses both the provided mapping and pattern matching
|
|
203
|
+
to handle various naming conventions.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
tool_name: The name of the tool from OpenAI
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
ActionType if mapped, None if unknown
|
|
210
|
+
"""
|
|
211
|
+
# Check exact match first
|
|
212
|
+
tool_name_lower = tool_name.lower()
|
|
213
|
+
if tool_name_lower in self.tool_mapping:
|
|
214
|
+
return self.tool_mapping[tool_name_lower]
|
|
215
|
+
|
|
216
|
+
# Pattern matching for common variations
|
|
217
|
+
if any(pattern in tool_name_lower for pattern in ['read', 'get', 'fetch', 'load']) and \
|
|
218
|
+
any(pattern in tool_name_lower for pattern in ['file', 'document']):
|
|
219
|
+
return ActionType.FILE_READ
|
|
220
|
+
|
|
221
|
+
if any(pattern in tool_name_lower for pattern in ['write', 'save', 'create', 'update']) and \
|
|
222
|
+
any(pattern in tool_name_lower for pattern in ['file', 'document']):
|
|
223
|
+
return ActionType.FILE_WRITE
|
|
224
|
+
|
|
225
|
+
if any(pattern in tool_name_lower for pattern in ['exec', 'run', 'execute', 'eval']) and \
|
|
226
|
+
any(pattern in tool_name_lower for pattern in ['code', 'python', 'script', 'command', 'bash']):
|
|
227
|
+
return ActionType.CODE_EXECUTION
|
|
228
|
+
|
|
229
|
+
if any(pattern in tool_name_lower for pattern in ['select', 'query', 'search']) and \
|
|
230
|
+
any(pattern in tool_name_lower for pattern in ['database', 'db', 'sql', 'table']):
|
|
231
|
+
return ActionType.DATABASE_QUERY
|
|
232
|
+
|
|
233
|
+
if any(pattern in tool_name_lower for pattern in ['insert', 'update', 'delete', 'drop', 'create', 'alter']) and \
|
|
234
|
+
any(pattern in tool_name_lower for pattern in ['database', 'db', 'sql', 'table']):
|
|
235
|
+
return ActionType.DATABASE_WRITE
|
|
236
|
+
|
|
237
|
+
if any(pattern in tool_name_lower for pattern in ['api', 'http', 'request', 'call', 'post', 'get']):
|
|
238
|
+
return ActionType.API_CALL
|
|
239
|
+
|
|
240
|
+
if any(pattern in tool_name_lower for pattern in ['workflow', 'trigger', 'pipeline']):
|
|
241
|
+
return ActionType.WORKFLOW_TRIGGER
|
|
242
|
+
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class ChatWrapper:
|
|
247
|
+
"""Wrapper for chat namespace"""
|
|
248
|
+
|
|
249
|
+
def __init__(
|
|
250
|
+
self,
|
|
251
|
+
original_chat: Any,
|
|
252
|
+
control_plane: AgentControlPlane,
|
|
253
|
+
agent_context: AgentContext,
|
|
254
|
+
tool_mapping: Dict[str, ActionType],
|
|
255
|
+
logger: logging.Logger,
|
|
256
|
+
on_block: Optional[Callable] = None
|
|
257
|
+
):
|
|
258
|
+
self.original = original_chat
|
|
259
|
+
self.completions = ChatCompletionsWrapper(
|
|
260
|
+
original_chat.completions,
|
|
261
|
+
control_plane,
|
|
262
|
+
agent_context,
|
|
263
|
+
tool_mapping,
|
|
264
|
+
logger,
|
|
265
|
+
on_block
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class ControlPlaneAdapter:
|
|
270
|
+
"""
|
|
271
|
+
OpenAI Client Adapter with Agent Control Plane Governance.
|
|
272
|
+
|
|
273
|
+
This class wraps an OpenAI client to provide automatic governance
|
|
274
|
+
of tool calls. It's designed as a drop-in replacement that requires
|
|
275
|
+
minimal code changes.
|
|
276
|
+
|
|
277
|
+
The adapter intercepts tool_calls in LLM responses and checks them
|
|
278
|
+
against the control plane's policies before allowing execution.
|
|
279
|
+
Blocked actions are replaced with error indicators following the
|
|
280
|
+
"Mute Agent" pattern.
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
# Before (ungovened):
|
|
284
|
+
client = OpenAI()
|
|
285
|
+
response = client.chat.completions.create(...)
|
|
286
|
+
|
|
287
|
+
# After (governed):
|
|
288
|
+
governed_client = ControlPlaneAdapter(control_plane, agent_context, client)
|
|
289
|
+
response = governed_client.chat.completions.create(...)
|
|
290
|
+
# Same API, but now with governance!
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
def __init__(
|
|
294
|
+
self,
|
|
295
|
+
control_plane: AgentControlPlane,
|
|
296
|
+
agent_context: AgentContext,
|
|
297
|
+
original_client: Any,
|
|
298
|
+
tool_mapping: Optional[Dict[str, ActionType]] = None,
|
|
299
|
+
on_block: Optional[Callable[[str, Dict, Dict], None]] = None,
|
|
300
|
+
logger: Optional[logging.Logger] = None
|
|
301
|
+
):
|
|
302
|
+
"""
|
|
303
|
+
Initialize the adapter.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
control_plane: The AgentControlPlane instance for governance
|
|
307
|
+
agent_context: The AgentContext for the agent using this client
|
|
308
|
+
original_client: The original OpenAI client instance
|
|
309
|
+
tool_mapping: Optional custom mapping from tool names to ActionTypes
|
|
310
|
+
on_block: Optional callback called when an action is blocked
|
|
311
|
+
Signature: on_block(tool_name: str, tool_args: dict, result: dict)
|
|
312
|
+
logger: Optional logger instance
|
|
313
|
+
"""
|
|
314
|
+
self.control_plane = control_plane
|
|
315
|
+
self.agent_context = agent_context
|
|
316
|
+
self.client = original_client
|
|
317
|
+
self.logger = logger or logging.getLogger("ControlPlaneAdapter")
|
|
318
|
+
self.on_block = on_block
|
|
319
|
+
|
|
320
|
+
# Merge default mapping with custom mapping
|
|
321
|
+
self.tool_mapping = DEFAULT_TOOL_MAPPING.copy()
|
|
322
|
+
if tool_mapping:
|
|
323
|
+
self.tool_mapping.update({k.lower(): v for k, v in tool_mapping.items()})
|
|
324
|
+
|
|
325
|
+
self.logger.info(
|
|
326
|
+
f"Initialized ControlPlaneAdapter for agent {agent_context.agent_id}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def chat(self) -> ChatWrapper:
|
|
331
|
+
"""Access the wrapped chat API"""
|
|
332
|
+
return ChatWrapper(
|
|
333
|
+
self.client.chat,
|
|
334
|
+
self.control_plane,
|
|
335
|
+
self.agent_context,
|
|
336
|
+
self.tool_mapping,
|
|
337
|
+
self.logger,
|
|
338
|
+
self.on_block
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
def add_tool_mapping(self, tool_name: str, action_type: ActionType):
|
|
342
|
+
"""
|
|
343
|
+
Add a custom tool name to ActionType mapping.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
tool_name: The name of the tool as used in OpenAI
|
|
347
|
+
action_type: The ActionType it should map to
|
|
348
|
+
"""
|
|
349
|
+
self.tool_mapping[tool_name.lower()] = action_type
|
|
350
|
+
self.logger.debug(f"Added tool mapping: {tool_name} -> {action_type.value}")
|
|
351
|
+
|
|
352
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
353
|
+
"""
|
|
354
|
+
Get statistics about the adapter's activity.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Dictionary with statistics from the control plane
|
|
358
|
+
"""
|
|
359
|
+
return {
|
|
360
|
+
"agent_id": self.agent_context.agent_id,
|
|
361
|
+
"session_id": self.agent_context.session_id,
|
|
362
|
+
"control_plane_audit": self.control_plane.get_audit_log(limit=100),
|
|
363
|
+
"execution_history": self.control_plane.get_execution_history(
|
|
364
|
+
agent_id=self.agent_context.agent_id,
|
|
365
|
+
limit=100
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def create_governed_client(
|
|
371
|
+
control_plane: AgentControlPlane,
|
|
372
|
+
agent_id: str,
|
|
373
|
+
openai_client: Any,
|
|
374
|
+
permissions: Optional[Dict[ActionType, Any]] = None,
|
|
375
|
+
tool_mapping: Optional[Dict[str, ActionType]] = None
|
|
376
|
+
) -> ControlPlaneAdapter:
|
|
377
|
+
"""
|
|
378
|
+
Convenience function to create a governed OpenAI client.
|
|
379
|
+
|
|
380
|
+
This creates both the agent in the control plane and the adapter,
|
|
381
|
+
providing a one-line setup for governed LLM interactions.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
control_plane: The AgentControlPlane instance
|
|
385
|
+
agent_id: ID for the agent
|
|
386
|
+
openai_client: The OpenAI client to wrap
|
|
387
|
+
permissions: Optional permissions for the agent
|
|
388
|
+
tool_mapping: Optional custom tool name mappings
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
A ControlPlaneAdapter ready to use
|
|
392
|
+
|
|
393
|
+
Example:
|
|
394
|
+
control_plane = AgentControlPlane()
|
|
395
|
+
openai_client = OpenAI(api_key="...")
|
|
396
|
+
|
|
397
|
+
governed = create_governed_client(
|
|
398
|
+
control_plane,
|
|
399
|
+
"my-agent",
|
|
400
|
+
openai_client,
|
|
401
|
+
permissions={
|
|
402
|
+
ActionType.FILE_READ: PermissionLevel.READ_ONLY,
|
|
403
|
+
ActionType.DATABASE_QUERY: PermissionLevel.READ_ONLY
|
|
404
|
+
}
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Use like normal OpenAI client
|
|
408
|
+
response = governed.chat.completions.create(...)
|
|
409
|
+
"""
|
|
410
|
+
agent_context = control_plane.create_agent(agent_id, permissions)
|
|
411
|
+
|
|
412
|
+
return ControlPlaneAdapter(
|
|
413
|
+
control_plane=control_plane,
|
|
414
|
+
agent_context=agent_context,
|
|
415
|
+
original_client=openai_client,
|
|
416
|
+
tool_mapping=tool_mapping
|
|
417
|
+
)
|