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
atr/executor.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Execution layer for Agent Tool Registry.
|
|
5
|
+
|
|
6
|
+
Provides sandboxed execution of tools using Docker containers.
|
|
7
|
+
This module handles the actual execution of registered tools, with support
|
|
8
|
+
for both local (unsafe) and Docker-based (safe) sandboxed execution.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from typing import Any, Callable, Dict, Optional
|
|
13
|
+
import tempfile
|
|
14
|
+
import os
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExecutorError(Exception):
|
|
19
|
+
"""Base exception for executor errors."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExecutionTimeoutError(ExecutorError):
|
|
24
|
+
"""Raised when execution exceeds timeout."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Executor(ABC):
|
|
29
|
+
"""Abstract base class for tool executors.
|
|
30
|
+
|
|
31
|
+
Executors handle the actual execution of registered tools,
|
|
32
|
+
providing different execution environments (local, Docker, etc.).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def execute(
|
|
37
|
+
self,
|
|
38
|
+
func: Callable[..., Any],
|
|
39
|
+
args: Optional[Dict[str, Any]] = None,
|
|
40
|
+
timeout: Optional[int] = None,
|
|
41
|
+
) -> Any:
|
|
42
|
+
"""Execute a callable function.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
func: The callable function to execute.
|
|
46
|
+
args: Dictionary of arguments to pass to the function.
|
|
47
|
+
timeout: Execution timeout in seconds (None for no timeout).
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The result of the function execution.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ExecutorError: If execution fails.
|
|
54
|
+
ExecutionTimeoutError: If execution exceeds timeout.
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LocalExecutor(Executor):
|
|
60
|
+
"""Executor that runs tools directly on the host machine.
|
|
61
|
+
|
|
62
|
+
WARNING: This executor provides no sandboxing and should only be used
|
|
63
|
+
with trusted code. For untrusted code, use DockerExecutor.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def execute(
|
|
67
|
+
self,
|
|
68
|
+
func: Callable[..., Any],
|
|
69
|
+
args: Optional[Dict[str, Any]] = None,
|
|
70
|
+
timeout: Optional[int] = None,
|
|
71
|
+
) -> Any:
|
|
72
|
+
"""Execute a callable function directly on the host.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
func: The callable function to execute.
|
|
76
|
+
args: Dictionary of arguments to pass to the function.
|
|
77
|
+
timeout: Execution timeout in seconds (ignored for local execution).
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
The result of the function execution.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ExecutorError: If execution fails.
|
|
84
|
+
"""
|
|
85
|
+
if args is None:
|
|
86
|
+
args = {}
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
return func(**args)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
raise ExecutorError(f"Local execution failed: {str(e)}") from e
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class DockerExecutor(Executor):
|
|
95
|
+
"""Executor that runs tools in isolated Docker containers.
|
|
96
|
+
|
|
97
|
+
This executor provides sandboxed execution by running tools inside
|
|
98
|
+
ephemeral Docker containers. Containers are automatically cleaned up
|
|
99
|
+
after execution completes or fails.
|
|
100
|
+
|
|
101
|
+
Attributes:
|
|
102
|
+
image: Docker image to use for execution (default: python:3.9-slim).
|
|
103
|
+
auto_pull: Whether to automatically pull the image if not available.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
image: str = "python:3.9-slim",
|
|
109
|
+
auto_pull: bool = True,
|
|
110
|
+
):
|
|
111
|
+
"""Initialize Docker executor.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
image: Docker image to use for execution.
|
|
115
|
+
auto_pull: Whether to automatically pull the image if not available.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
ImportError: If docker package is not installed.
|
|
119
|
+
ExecutorError: If Docker daemon is not accessible.
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
import docker
|
|
123
|
+
except ImportError:
|
|
124
|
+
raise ImportError(
|
|
125
|
+
"docker package is required for DockerExecutor. "
|
|
126
|
+
"Install it with: pip install docker"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
self.image = image
|
|
130
|
+
self.auto_pull = auto_pull
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
self._client = docker.from_env()
|
|
134
|
+
# Test Docker connection
|
|
135
|
+
self._client.ping()
|
|
136
|
+
except Exception as e:
|
|
137
|
+
raise ExecutorError(
|
|
138
|
+
f"Failed to connect to Docker daemon: {str(e)}. "
|
|
139
|
+
"Make sure Docker is installed and running."
|
|
140
|
+
) from e
|
|
141
|
+
|
|
142
|
+
# Pull image if needed
|
|
143
|
+
if self.auto_pull:
|
|
144
|
+
self._ensure_image()
|
|
145
|
+
|
|
146
|
+
def _ensure_image(self) -> None:
|
|
147
|
+
"""Ensure the Docker image is available, pulling if necessary."""
|
|
148
|
+
try:
|
|
149
|
+
self._client.images.get(self.image)
|
|
150
|
+
except Exception:
|
|
151
|
+
# Image not found, try to pull it
|
|
152
|
+
try:
|
|
153
|
+
self._client.images.pull(self.image)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
raise ExecutorError(
|
|
156
|
+
f"Failed to pull Docker image '{self.image}': {str(e)}"
|
|
157
|
+
) from e
|
|
158
|
+
|
|
159
|
+
def execute(
|
|
160
|
+
self,
|
|
161
|
+
func: Callable[..., Any],
|
|
162
|
+
args: Optional[Dict[str, Any]] = None,
|
|
163
|
+
timeout: Optional[int] = None,
|
|
164
|
+
) -> Any:
|
|
165
|
+
"""Execute a callable function in a Docker container.
|
|
166
|
+
|
|
167
|
+
The function is serialized and executed inside an ephemeral container.
|
|
168
|
+
The container is automatically removed after execution.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
func: The callable function to execute.
|
|
172
|
+
args: Dictionary of arguments to pass to the function.
|
|
173
|
+
timeout: Execution timeout in seconds (None for no timeout).
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
The result of the function execution.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
ExecutorError: If execution fails.
|
|
180
|
+
ExecutionTimeoutError: If execution exceeds timeout.
|
|
181
|
+
"""
|
|
182
|
+
if args is None:
|
|
183
|
+
args = {}
|
|
184
|
+
|
|
185
|
+
# Import required modules
|
|
186
|
+
import docker
|
|
187
|
+
import inspect
|
|
188
|
+
import textwrap
|
|
189
|
+
|
|
190
|
+
container = None
|
|
191
|
+
try:
|
|
192
|
+
# Get the function source
|
|
193
|
+
try:
|
|
194
|
+
full_source = inspect.getsource(func)
|
|
195
|
+
except (OSError, TypeError):
|
|
196
|
+
raise ExecutorError(
|
|
197
|
+
"Cannot execute function in Docker: source code unavailable. "
|
|
198
|
+
"This typically happens with built-in functions or lambdas."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
func_name = func.__name__
|
|
202
|
+
|
|
203
|
+
# Extract just the function definition (skip decorators)
|
|
204
|
+
# Split into lines and find where the def statement starts
|
|
205
|
+
lines = full_source.split('\n')
|
|
206
|
+
func_start_idx = 0
|
|
207
|
+
for i, line in enumerate(lines):
|
|
208
|
+
if line.strip().startswith('def '):
|
|
209
|
+
func_start_idx = i
|
|
210
|
+
break
|
|
211
|
+
|
|
212
|
+
# Get the function definition without decorators
|
|
213
|
+
func_lines = lines[func_start_idx:]
|
|
214
|
+
|
|
215
|
+
# Dedent to remove any leading indentation
|
|
216
|
+
func_source = textwrap.dedent('\n'.join(func_lines))
|
|
217
|
+
|
|
218
|
+
# Create execution script
|
|
219
|
+
script = self._create_execution_script(func_source, func_name, args)
|
|
220
|
+
|
|
221
|
+
# Create temporary directory for script
|
|
222
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
223
|
+
script_path = os.path.join(temp_dir, "execute.py")
|
|
224
|
+
with open(script_path, "w") as f:
|
|
225
|
+
f.write(script)
|
|
226
|
+
|
|
227
|
+
# Create container with volume mount
|
|
228
|
+
container = self._client.containers.create(
|
|
229
|
+
image=self.image,
|
|
230
|
+
command=["python", "/app/execute.py"],
|
|
231
|
+
volumes={temp_dir: {"bind": "/app", "mode": "ro"}},
|
|
232
|
+
network_mode="none", # Disable network for security
|
|
233
|
+
mem_limit="512m", # Limit memory
|
|
234
|
+
detach=True,
|
|
235
|
+
auto_remove=False, # We'll remove manually after getting logs
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Start container
|
|
239
|
+
container.start()
|
|
240
|
+
|
|
241
|
+
# Wait for completion with timeout
|
|
242
|
+
try:
|
|
243
|
+
exit_code = container.wait(timeout=timeout)
|
|
244
|
+
|
|
245
|
+
# Get container status
|
|
246
|
+
if isinstance(exit_code, dict):
|
|
247
|
+
exit_code = exit_code.get("StatusCode", 0)
|
|
248
|
+
|
|
249
|
+
# Get logs
|
|
250
|
+
logs = container.logs().decode("utf-8")
|
|
251
|
+
|
|
252
|
+
if exit_code != 0:
|
|
253
|
+
raise ExecutorError(
|
|
254
|
+
f"Container execution failed with exit code {exit_code}:\n{logs}"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Parse result from logs
|
|
258
|
+
result = self._parse_result(logs)
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
# Check if it's a timeout
|
|
263
|
+
error_str = str(e).lower()
|
|
264
|
+
if "timeout" in error_str or "timed out" in error_str:
|
|
265
|
+
raise ExecutionTimeoutError(
|
|
266
|
+
f"Execution exceeded timeout of {timeout} seconds"
|
|
267
|
+
) from e
|
|
268
|
+
# Re-raise if it's already one of our exception types
|
|
269
|
+
if isinstance(e, (ExecutorError, ExecutionTimeoutError)):
|
|
270
|
+
raise
|
|
271
|
+
# Wrap other exceptions
|
|
272
|
+
raise ExecutorError(f"Docker execution failed: {str(e)}") from e
|
|
273
|
+
|
|
274
|
+
except ExecutorError:
|
|
275
|
+
raise
|
|
276
|
+
except ExecutionTimeoutError:
|
|
277
|
+
raise
|
|
278
|
+
except Exception as e:
|
|
279
|
+
raise ExecutorError(f"Docker execution failed: {str(e)}") from e
|
|
280
|
+
finally:
|
|
281
|
+
# Clean up container
|
|
282
|
+
if container is not None:
|
|
283
|
+
try:
|
|
284
|
+
container.stop(timeout=1)
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|
|
287
|
+
try:
|
|
288
|
+
container.remove(force=True)
|
|
289
|
+
except Exception:
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
def _create_execution_script(
|
|
293
|
+
self,
|
|
294
|
+
func_source: str,
|
|
295
|
+
func_name: str,
|
|
296
|
+
args: Dict[str, Any],
|
|
297
|
+
) -> str:
|
|
298
|
+
"""Create Python script for container execution.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
func_source: Source code of the function.
|
|
302
|
+
func_name: Name of the function.
|
|
303
|
+
args: Arguments to pass to the function.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Complete Python script as string.
|
|
307
|
+
"""
|
|
308
|
+
# Serialize args to JSON and escape for embedding in script
|
|
309
|
+
args_json = json.dumps(args).replace('\\', '\\\\').replace("'", "\\'")
|
|
310
|
+
|
|
311
|
+
# Build the script with common imports
|
|
312
|
+
script_parts = [
|
|
313
|
+
"import json",
|
|
314
|
+
"import sys",
|
|
315
|
+
"from typing import List, Dict, Optional, Any, Tuple, Set", # Common type hints
|
|
316
|
+
"",
|
|
317
|
+
"# Define the function",
|
|
318
|
+
func_source,
|
|
319
|
+
"",
|
|
320
|
+
"# Parse arguments",
|
|
321
|
+
f"args = json.loads('{args_json}')",
|
|
322
|
+
"",
|
|
323
|
+
"# Execute function",
|
|
324
|
+
"try:",
|
|
325
|
+
f" result = {func_name}(**args)",
|
|
326
|
+
" # Print result with marker for parsing",
|
|
327
|
+
' print("__RESULT_START__")',
|
|
328
|
+
' print(json.dumps({"success": True, "result": result}))',
|
|
329
|
+
' print("__RESULT_END__")',
|
|
330
|
+
"except Exception as e:",
|
|
331
|
+
' print("__RESULT_START__")',
|
|
332
|
+
' print(json.dumps({"success": False, "error": str(e)}))',
|
|
333
|
+
' print("__RESULT_END__")',
|
|
334
|
+
" sys.exit(1)",
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
return "\n".join(script_parts)
|
|
338
|
+
|
|
339
|
+
def _parse_result(self, logs: str) -> Any:
|
|
340
|
+
"""Parse execution result from container logs.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
logs: Container logs output.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
The execution result.
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
ExecutorError: If result parsing fails.
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
# Find result between markers
|
|
353
|
+
start_marker = "__RESULT_START__"
|
|
354
|
+
end_marker = "__RESULT_END__"
|
|
355
|
+
|
|
356
|
+
if start_marker not in logs or end_marker not in logs:
|
|
357
|
+
raise ExecutorError(f"Could not find result markers in logs:\n{logs}")
|
|
358
|
+
|
|
359
|
+
start_idx = logs.index(start_marker) + len(start_marker)
|
|
360
|
+
end_idx = logs.index(end_marker)
|
|
361
|
+
result_json = logs[start_idx:end_idx].strip()
|
|
362
|
+
|
|
363
|
+
result_data = json.loads(result_json)
|
|
364
|
+
|
|
365
|
+
if not result_data.get("success"):
|
|
366
|
+
error = result_data.get("error", "Unknown error")
|
|
367
|
+
raise ExecutorError(f"Function execution failed: {error}")
|
|
368
|
+
|
|
369
|
+
return result_data.get("result")
|
|
370
|
+
|
|
371
|
+
except json.JSONDecodeError as e:
|
|
372
|
+
raise ExecutorError(f"Failed to parse result JSON: {str(e)}") from e
|
|
373
|
+
except ExecutorError:
|
|
374
|
+
raise
|
|
375
|
+
except Exception as e:
|
|
376
|
+
raise ExecutorError(f"Failed to parse result: {str(e)}") from e
|
|
377
|
+
|
|
378
|
+
def __del__(self):
|
|
379
|
+
"""Clean up Docker client."""
|
|
380
|
+
if hasattr(self, "_client"):
|
|
381
|
+
try:
|
|
382
|
+
self._client.close()
|
|
383
|
+
except Exception:
|
|
384
|
+
pass
|