agent-runtime-kit 0.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_runtime_kit/__init__.py +72 -0
- agent_runtime_kit/_errors.py +34 -0
- agent_runtime_kit/_runtime.py +139 -0
- agent_runtime_kit/_types.py +251 -0
- agent_runtime_kit/adapters/__init__.py +26 -0
- agent_runtime_kit/adapters/_common.py +123 -0
- agent_runtime_kit/adapters/antigravity.py +379 -0
- agent_runtime_kit/adapters/claude.py +302 -0
- agent_runtime_kit/adapters/codex.py +298 -0
- agent_runtime_kit/adapters/diagnostics.py +18 -0
- agent_runtime_kit/events.py +224 -0
- agent_runtime_kit/py.typed +1 -0
- agent_runtime_kit/registry.py +83 -0
- agent_runtime_kit/testing/__init__.py +15 -0
- agent_runtime_kit/testing/fakes.py +164 -0
- agent_runtime_kit-0.1.0.dist-info/METADATA +118 -0
- agent_runtime_kit-0.1.0.dist-info/RECORD +19 -0
- agent_runtime_kit-0.1.0.dist-info/WHEEL +4 -0
- agent_runtime_kit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Public API for agent-runtime-kit."""
|
|
2
|
+
|
|
3
|
+
from agent_runtime_kit._errors import (
|
|
4
|
+
AgentRuntimeError,
|
|
5
|
+
AgentRuntimeUnavailableError,
|
|
6
|
+
RuntimeNotRegisteredError,
|
|
7
|
+
UnsupportedTaskInputError,
|
|
8
|
+
)
|
|
9
|
+
from agent_runtime_kit._runtime import FakeAgentRuntime
|
|
10
|
+
from agent_runtime_kit._types import (
|
|
11
|
+
AgentCapabilities,
|
|
12
|
+
AgentResult,
|
|
13
|
+
AgentRuntime,
|
|
14
|
+
AgentRuntimeKind,
|
|
15
|
+
AgentTask,
|
|
16
|
+
ArtifactRef,
|
|
17
|
+
AvailabilityReason,
|
|
18
|
+
EventSink,
|
|
19
|
+
FilesystemAccess,
|
|
20
|
+
McpServerConfig,
|
|
21
|
+
PermissionMode,
|
|
22
|
+
PermissionProfile,
|
|
23
|
+
RuntimeAvailability,
|
|
24
|
+
SessionResumeState,
|
|
25
|
+
ToolCallAudit,
|
|
26
|
+
Usage,
|
|
27
|
+
)
|
|
28
|
+
from agent_runtime_kit.events import (
|
|
29
|
+
output_delta_event,
|
|
30
|
+
safe_emit,
|
|
31
|
+
task_completed_event,
|
|
32
|
+
task_failed_event,
|
|
33
|
+
task_started_event,
|
|
34
|
+
tool_completed_event,
|
|
35
|
+
tool_requested_event,
|
|
36
|
+
vendor_turn_event,
|
|
37
|
+
)
|
|
38
|
+
from agent_runtime_kit.registry import RuntimeRegistry, create_default_registry
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"AgentCapabilities",
|
|
42
|
+
"AgentResult",
|
|
43
|
+
"AgentRuntime",
|
|
44
|
+
"AgentRuntimeError",
|
|
45
|
+
"AgentRuntimeKind",
|
|
46
|
+
"AgentRuntimeUnavailableError",
|
|
47
|
+
"AgentTask",
|
|
48
|
+
"ArtifactRef",
|
|
49
|
+
"AvailabilityReason",
|
|
50
|
+
"EventSink",
|
|
51
|
+
"FakeAgentRuntime",
|
|
52
|
+
"FilesystemAccess",
|
|
53
|
+
"McpServerConfig",
|
|
54
|
+
"PermissionMode",
|
|
55
|
+
"PermissionProfile",
|
|
56
|
+
"RuntimeAvailability",
|
|
57
|
+
"RuntimeNotRegisteredError",
|
|
58
|
+
"RuntimeRegistry",
|
|
59
|
+
"SessionResumeState",
|
|
60
|
+
"ToolCallAudit",
|
|
61
|
+
"UnsupportedTaskInputError",
|
|
62
|
+
"Usage",
|
|
63
|
+
"create_default_registry",
|
|
64
|
+
"output_delta_event",
|
|
65
|
+
"safe_emit",
|
|
66
|
+
"task_completed_event",
|
|
67
|
+
"task_failed_event",
|
|
68
|
+
"task_started_event",
|
|
69
|
+
"tool_completed_event",
|
|
70
|
+
"tool_requested_event",
|
|
71
|
+
"vendor_turn_event",
|
|
72
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Typed errors raised by agent runtimes and registries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from agent_runtime_kit._types import AgentRuntimeKind
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AgentRuntimeError(RuntimeError):
|
|
9
|
+
"""Base class for runtime-layer failures."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentRuntimeUnavailableError(AgentRuntimeError):
|
|
13
|
+
"""A runtime cannot be constructed or used in the current environment."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, kind: AgentRuntimeKind | str, message: str) -> None:
|
|
16
|
+
self.kind = AgentRuntimeKind.coerce(kind)
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UnsupportedTaskInputError(AgentRuntimeError, ValueError):
|
|
21
|
+
"""A runtime was asked to honor an input it does not support."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, kind: AgentRuntimeKind | str, field: str, message: str) -> None:
|
|
24
|
+
self.kind = AgentRuntimeKind.coerce(kind)
|
|
25
|
+
self.field = field
|
|
26
|
+
super().__init__(f"{self.kind.value} cannot honor {field}: {message}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RuntimeNotRegisteredError(AgentRuntimeError, LookupError):
|
|
30
|
+
"""No runtime factory is registered for the requested runtime kind."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, kind: AgentRuntimeKind | str) -> None:
|
|
33
|
+
self.kind = AgentRuntimeKind.coerce(kind)
|
|
34
|
+
super().__init__(f"No runtime registered for {self.kind.value!r}")
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Dependency-free runtime implementations used by tests and examples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from agent_runtime_kit._errors import UnsupportedTaskInputError
|
|
9
|
+
from agent_runtime_kit._types import (
|
|
10
|
+
AgentCapabilities,
|
|
11
|
+
AgentResult,
|
|
12
|
+
AgentRuntimeKind,
|
|
13
|
+
AgentTask,
|
|
14
|
+
RuntimeAvailability,
|
|
15
|
+
ToolCallAudit,
|
|
16
|
+
)
|
|
17
|
+
from agent_runtime_kit.events import (
|
|
18
|
+
output_delta_event,
|
|
19
|
+
safe_emit,
|
|
20
|
+
task_completed_event,
|
|
21
|
+
task_failed_event,
|
|
22
|
+
task_started_event,
|
|
23
|
+
tool_completed_event,
|
|
24
|
+
tool_requested_event,
|
|
25
|
+
vendor_turn_event,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FakeAgentRuntime:
|
|
30
|
+
"""Small deterministic runtime for tests and local examples."""
|
|
31
|
+
|
|
32
|
+
kind = AgentRuntimeKind.FAKE
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
output: str | None = None,
|
|
38
|
+
capabilities: AgentCapabilities | None = None,
|
|
39
|
+
metadata: Mapping[str, Any] | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
self.capabilities = capabilities or AgentCapabilities(
|
|
42
|
+
mcp_support=True,
|
|
43
|
+
working_directory=True,
|
|
44
|
+
session_resume=True,
|
|
45
|
+
structured_output=True,
|
|
46
|
+
streaming=False,
|
|
47
|
+
tool_audit=True,
|
|
48
|
+
cancellation=True,
|
|
49
|
+
)
|
|
50
|
+
self._output = output
|
|
51
|
+
self._metadata = dict(metadata or {})
|
|
52
|
+
self.cancelled_task_ids: set[str] = set()
|
|
53
|
+
|
|
54
|
+
def availability(self) -> RuntimeAvailability:
|
|
55
|
+
"""Fake runtime is always available."""
|
|
56
|
+
|
|
57
|
+
return RuntimeAvailability.ok(self.kind, package="agent-runtime-kit")
|
|
58
|
+
|
|
59
|
+
async def run(self, task: AgentTask) -> AgentResult:
|
|
60
|
+
"""Return a deterministic result after validating capabilities."""
|
|
61
|
+
|
|
62
|
+
await safe_emit(task, task_started_event(task, self.kind))
|
|
63
|
+
try:
|
|
64
|
+
_ensure_supported(self.kind, self.capabilities, task)
|
|
65
|
+
output = self._output if self._output is not None else f"Fake result for: {task.goal}"
|
|
66
|
+
parsed = {"output": output} if task.output_schema is not None else None
|
|
67
|
+
tool_call = ToolCallAudit(
|
|
68
|
+
tool_name="fake",
|
|
69
|
+
arguments={"goal": task.goal},
|
|
70
|
+
result_preview=output,
|
|
71
|
+
)
|
|
72
|
+
await safe_emit(
|
|
73
|
+
task,
|
|
74
|
+
output_delta_event(task, self.kind, text=output),
|
|
75
|
+
)
|
|
76
|
+
await safe_emit(
|
|
77
|
+
task,
|
|
78
|
+
tool_requested_event(
|
|
79
|
+
task,
|
|
80
|
+
self.kind,
|
|
81
|
+
tool_name="fake",
|
|
82
|
+
arguments=tool_call.arguments,
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
await safe_emit(task, tool_completed_event(task, self.kind, tool_call))
|
|
86
|
+
await safe_emit(
|
|
87
|
+
task,
|
|
88
|
+
vendor_turn_event(
|
|
89
|
+
task,
|
|
90
|
+
self.kind,
|
|
91
|
+
payload={"runtime": "fake", "round": 1},
|
|
92
|
+
summary="fake runtime completed one turn",
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
result = AgentResult(
|
|
96
|
+
output=output,
|
|
97
|
+
parsed_output=parsed,
|
|
98
|
+
tool_calls=(tool_call,),
|
|
99
|
+
session_id=task.session_id or task.task_id,
|
|
100
|
+
rounds=1,
|
|
101
|
+
metadata={"task_id": task.task_id, **self._metadata},
|
|
102
|
+
)
|
|
103
|
+
except Exception as exc:
|
|
104
|
+
await safe_emit(task, task_failed_event(task, self.kind, error=str(exc)))
|
|
105
|
+
raise
|
|
106
|
+
await safe_emit(task, task_completed_event(task, self.kind, result))
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
async def cancel(self, task_id: str) -> None:
|
|
110
|
+
"""Record cancellation requests for assertions."""
|
|
111
|
+
|
|
112
|
+
self.cancelled_task_ids.add(task_id)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _ensure_supported(
|
|
116
|
+
kind: AgentRuntimeKind,
|
|
117
|
+
capabilities: AgentCapabilities,
|
|
118
|
+
task: AgentTask,
|
|
119
|
+
) -> None:
|
|
120
|
+
if task.mcp_servers and not capabilities.mcp_support:
|
|
121
|
+
raise UnsupportedTaskInputError(kind, "mcp_servers", "runtime does not support MCP")
|
|
122
|
+
if task.working_directory is not None and not capabilities.working_directory:
|
|
123
|
+
raise UnsupportedTaskInputError(
|
|
124
|
+
kind,
|
|
125
|
+
"working_directory",
|
|
126
|
+
"runtime does not support per-task working directories",
|
|
127
|
+
)
|
|
128
|
+
if (task.session_id or task.resume_from) and not capabilities.session_resume:
|
|
129
|
+
raise UnsupportedTaskInputError(
|
|
130
|
+
kind,
|
|
131
|
+
"session_id",
|
|
132
|
+
"runtime does not support session resume",
|
|
133
|
+
)
|
|
134
|
+
if task.output_schema is not None and not capabilities.structured_output:
|
|
135
|
+
raise UnsupportedTaskInputError(
|
|
136
|
+
kind,
|
|
137
|
+
"output_schema",
|
|
138
|
+
"runtime does not support structured output",
|
|
139
|
+
)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Core public models and protocols."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Protocol, runtime_checkable
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentRuntimeKind(str, Enum):
|
|
14
|
+
"""Supported runtime families."""
|
|
15
|
+
|
|
16
|
+
FAKE = "fake"
|
|
17
|
+
CLAUDE_AGENT_SDK = "claude-agent-sdk"
|
|
18
|
+
CODEX_AGENT_SDK = "codex-agent-sdk"
|
|
19
|
+
ANTIGRAVITY_AGENT_SDK = "antigravity-agent-sdk"
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def coerce(cls, value: AgentRuntimeKind | str) -> AgentRuntimeKind:
|
|
23
|
+
"""Normalize a string or enum value into an ``AgentRuntimeKind``."""
|
|
24
|
+
|
|
25
|
+
if isinstance(value, cls):
|
|
26
|
+
return value
|
|
27
|
+
return cls(value)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AvailabilityReason(str, Enum):
|
|
31
|
+
"""Why a runtime is, or is not, available."""
|
|
32
|
+
|
|
33
|
+
AVAILABLE = "available"
|
|
34
|
+
MISSING_PACKAGE = "missing-package"
|
|
35
|
+
MISSING_CREDENTIALS = "missing-credentials"
|
|
36
|
+
UNSUPPORTED_MODEL = "unsupported-model"
|
|
37
|
+
SETUP_FAILED = "setup-failed"
|
|
38
|
+
UNKNOWN = "unknown"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PermissionMode(str, Enum):
|
|
42
|
+
"""High-level permission intent for vendor runtimes."""
|
|
43
|
+
|
|
44
|
+
DEFAULT = "default"
|
|
45
|
+
STRICT = "strict"
|
|
46
|
+
CAUTIOUS = "cautious"
|
|
47
|
+
PERMISSIVE = "permissive"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class FilesystemAccess(str, Enum):
|
|
51
|
+
"""Filesystem mutation level requested by a task."""
|
|
52
|
+
|
|
53
|
+
READ_ONLY = "read-only"
|
|
54
|
+
WORKSPACE_WRITE = "workspace-write"
|
|
55
|
+
FULL_ACCESS = "full-access"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@runtime_checkable
|
|
59
|
+
class EventSink(Protocol):
|
|
60
|
+
"""Async destination for normalized runtime events."""
|
|
61
|
+
|
|
62
|
+
async def emit(self, event: Mapping[str, Any]) -> None:
|
|
63
|
+
"""Receive one normalized event."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class AgentCapabilities:
|
|
68
|
+
"""Runtime capability advertisement."""
|
|
69
|
+
|
|
70
|
+
mcp_support: bool = False
|
|
71
|
+
working_directory: bool = False
|
|
72
|
+
session_resume: bool = False
|
|
73
|
+
structured_output: bool = False
|
|
74
|
+
streaming: bool = False
|
|
75
|
+
tool_audit: bool = False
|
|
76
|
+
sdk_turn_limit: bool = False
|
|
77
|
+
cancellation: bool = False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class RuntimeAvailability:
|
|
82
|
+
"""Availability diagnostic for a runtime in the current environment."""
|
|
83
|
+
|
|
84
|
+
kind: AgentRuntimeKind
|
|
85
|
+
available: bool
|
|
86
|
+
reason: AvailabilityReason = AvailabilityReason.UNKNOWN
|
|
87
|
+
message: str = ""
|
|
88
|
+
package: str | None = None
|
|
89
|
+
version: str | None = None
|
|
90
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def ok(
|
|
94
|
+
cls,
|
|
95
|
+
kind: AgentRuntimeKind | str,
|
|
96
|
+
*,
|
|
97
|
+
package: str | None = None,
|
|
98
|
+
version: str | None = None,
|
|
99
|
+
metadata: Mapping[str, Any] | None = None,
|
|
100
|
+
) -> RuntimeAvailability:
|
|
101
|
+
"""Build a positive availability result."""
|
|
102
|
+
|
|
103
|
+
return cls(
|
|
104
|
+
kind=AgentRuntimeKind.coerce(kind),
|
|
105
|
+
available=True,
|
|
106
|
+
reason=AvailabilityReason.AVAILABLE,
|
|
107
|
+
message="available",
|
|
108
|
+
package=package,
|
|
109
|
+
version=version,
|
|
110
|
+
metadata=dict(metadata or {}),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def unavailable(
|
|
115
|
+
cls,
|
|
116
|
+
kind: AgentRuntimeKind | str,
|
|
117
|
+
*,
|
|
118
|
+
reason: AvailabilityReason,
|
|
119
|
+
message: str,
|
|
120
|
+
package: str | None = None,
|
|
121
|
+
metadata: Mapping[str, Any] | None = None,
|
|
122
|
+
) -> RuntimeAvailability:
|
|
123
|
+
"""Build a negative availability result."""
|
|
124
|
+
|
|
125
|
+
return cls(
|
|
126
|
+
kind=AgentRuntimeKind.coerce(kind),
|
|
127
|
+
available=False,
|
|
128
|
+
reason=reason,
|
|
129
|
+
message=message,
|
|
130
|
+
package=package,
|
|
131
|
+
metadata=dict(metadata or {}),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True)
|
|
136
|
+
class McpServerConfig:
|
|
137
|
+
"""Configuration for a stdio MCP server owned by a vendor runtime."""
|
|
138
|
+
|
|
139
|
+
name: str
|
|
140
|
+
command: str
|
|
141
|
+
args: tuple[str, ...] = ()
|
|
142
|
+
env: Mapping[str, str] = field(default_factory=dict)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(frozen=True)
|
|
146
|
+
class PermissionProfile:
|
|
147
|
+
"""Portable permission request mapped by each adapter."""
|
|
148
|
+
|
|
149
|
+
mode: PermissionMode = PermissionMode.DEFAULT
|
|
150
|
+
filesystem: FilesystemAccess = FilesystemAccess.WORKSPACE_WRITE
|
|
151
|
+
allowed_tools: tuple[str, ...] = ()
|
|
152
|
+
disallowed_tools: tuple[str, ...] = ()
|
|
153
|
+
network: bool | None = None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass(frozen=True)
|
|
157
|
+
class ToolCallAudit:
|
|
158
|
+
"""Best-effort audit entry for one vendor-observed tool invocation."""
|
|
159
|
+
|
|
160
|
+
tool_name: str
|
|
161
|
+
arguments: Mapping[str, Any] = field(default_factory=dict)
|
|
162
|
+
result_preview: str = ""
|
|
163
|
+
status: str = "ok"
|
|
164
|
+
duration_ms: int = 0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass(frozen=True)
|
|
168
|
+
class ArtifactRef:
|
|
169
|
+
"""Reference to an artifact produced by a runtime."""
|
|
170
|
+
|
|
171
|
+
uri: str
|
|
172
|
+
kind: str = "file"
|
|
173
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass(frozen=True)
|
|
177
|
+
class SessionResumeState:
|
|
178
|
+
"""Opaque session handle carried between invocations."""
|
|
179
|
+
|
|
180
|
+
session_id: str
|
|
181
|
+
transcript: tuple[Any, ...] = ()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass(frozen=True)
|
|
185
|
+
class Usage:
|
|
186
|
+
"""Token and cost metadata reported by a runtime."""
|
|
187
|
+
|
|
188
|
+
input_tokens: int = 0
|
|
189
|
+
output_tokens: int = 0
|
|
190
|
+
cache_read_tokens: int = 0
|
|
191
|
+
cache_creation_tokens: int = 0
|
|
192
|
+
total_tokens: int | None = None
|
|
193
|
+
cost_usd: float = 0.0
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass(frozen=True)
|
|
197
|
+
class AgentTask:
|
|
198
|
+
"""One task dispatched to an agent runtime."""
|
|
199
|
+
|
|
200
|
+
goal: str
|
|
201
|
+
task_id: str = field(default_factory=lambda: f"task-{uuid4().hex}")
|
|
202
|
+
system: str | None = None
|
|
203
|
+
working_directory: Path | None = None
|
|
204
|
+
mcp_servers: tuple[McpServerConfig, ...] = ()
|
|
205
|
+
permissions: PermissionProfile = field(default_factory=PermissionProfile)
|
|
206
|
+
event_sink: EventSink | None = None
|
|
207
|
+
sdk_executions: int = 1
|
|
208
|
+
budget_usd: float | None = None
|
|
209
|
+
session_id: str | None = None
|
|
210
|
+
resume_from: SessionResumeState | None = None
|
|
211
|
+
output_schema: Mapping[str, Any] | None = None
|
|
212
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass(frozen=True)
|
|
216
|
+
class AgentResult:
|
|
217
|
+
"""Typed result returned by all runtimes."""
|
|
218
|
+
|
|
219
|
+
output: str
|
|
220
|
+
finish_reason: str = "done"
|
|
221
|
+
error: str | None = None
|
|
222
|
+
parsed_output: Any | None = None
|
|
223
|
+
usage: Usage = field(default_factory=Usage)
|
|
224
|
+
tool_calls: tuple[ToolCallAudit, ...] = ()
|
|
225
|
+
artifacts: tuple[ArtifactRef, ...] = ()
|
|
226
|
+
session_id: str | None = None
|
|
227
|
+
rounds: int = 0
|
|
228
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def cost_usd(self) -> float:
|
|
232
|
+
"""Return the reported task cost in USD."""
|
|
233
|
+
|
|
234
|
+
return self.usage.cost_usd
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@runtime_checkable
|
|
238
|
+
class AgentRuntime(Protocol):
|
|
239
|
+
"""Async runtime that drives an ``AgentTask`` to completion."""
|
|
240
|
+
|
|
241
|
+
kind: AgentRuntimeKind
|
|
242
|
+
capabilities: AgentCapabilities
|
|
243
|
+
|
|
244
|
+
def availability(self) -> RuntimeAvailability:
|
|
245
|
+
"""Report whether this runtime can execute in the current environment."""
|
|
246
|
+
|
|
247
|
+
async def run(self, task: AgentTask) -> AgentResult:
|
|
248
|
+
"""Execute one task."""
|
|
249
|
+
|
|
250
|
+
async def cancel(self, task_id: str) -> None:
|
|
251
|
+
"""Request cancellation for a task if supported."""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Vendor adapter modules."""
|
|
2
|
+
|
|
3
|
+
from agent_runtime_kit._types import AgentRuntimeKind
|
|
4
|
+
from agent_runtime_kit.adapters.antigravity import AntigravityAgentRuntime
|
|
5
|
+
from agent_runtime_kit.adapters.claude import ClaudeAgentRuntime
|
|
6
|
+
from agent_runtime_kit.adapters.codex import CodexAgentRuntime
|
|
7
|
+
from agent_runtime_kit.registry import RuntimeRegistry
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AntigravityAgentRuntime",
|
|
11
|
+
"ClaudeAgentRuntime",
|
|
12
|
+
"CodexAgentRuntime",
|
|
13
|
+
"register_adapters",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_adapters(registry: RuntimeRegistry, *, replace: bool = False) -> None:
|
|
18
|
+
"""Register the built-in vendor adapters in a runtime registry."""
|
|
19
|
+
|
|
20
|
+
registry.register(AgentRuntimeKind.CLAUDE_AGENT_SDK, ClaudeAgentRuntime, replace=replace)
|
|
21
|
+
registry.register(AgentRuntimeKind.CODEX_AGENT_SDK, CodexAgentRuntime, replace=replace)
|
|
22
|
+
registry.register(
|
|
23
|
+
AgentRuntimeKind.ANTIGRAVITY_AGENT_SDK,
|
|
24
|
+
AntigravityAgentRuntime,
|
|
25
|
+
replace=replace,
|
|
26
|
+
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Shared helpers for optional vendor adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import json
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from importlib import metadata, util
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from agent_runtime_kit._errors import UnsupportedTaskInputError
|
|
12
|
+
from agent_runtime_kit._types import (
|
|
13
|
+
AgentRuntimeKind,
|
|
14
|
+
AvailabilityReason,
|
|
15
|
+
RuntimeAvailability,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def package_availability(
|
|
20
|
+
kind: AgentRuntimeKind,
|
|
21
|
+
*,
|
|
22
|
+
module_name: str,
|
|
23
|
+
package_name: str,
|
|
24
|
+
) -> RuntimeAvailability:
|
|
25
|
+
"""Return import/package availability without importing the package."""
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
module_spec = util.find_spec(module_name)
|
|
29
|
+
except ModuleNotFoundError:
|
|
30
|
+
module_spec = None
|
|
31
|
+
if module_spec is None:
|
|
32
|
+
return RuntimeAvailability.unavailable(
|
|
33
|
+
kind,
|
|
34
|
+
reason=AvailabilityReason.MISSING_PACKAGE,
|
|
35
|
+
message=f"Install the optional dependency: agent-runtime-kit[{_extra_name(kind)}]",
|
|
36
|
+
package=package_name,
|
|
37
|
+
)
|
|
38
|
+
return RuntimeAvailability.ok(
|
|
39
|
+
kind,
|
|
40
|
+
package=package_name,
|
|
41
|
+
version=package_version(package_name),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def package_version(package_name: str) -> str | None:
|
|
46
|
+
"""Return installed distribution version, if available."""
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
return metadata.version(package_name)
|
|
50
|
+
except metadata.PackageNotFoundError:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def ensure_supported_model(
|
|
55
|
+
*,
|
|
56
|
+
kind: AgentRuntimeKind,
|
|
57
|
+
model: str,
|
|
58
|
+
supported_models: tuple[str, ...] | None,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Raise a typed error when a runtime was configured with an allow-list."""
|
|
61
|
+
|
|
62
|
+
if supported_models is None or model in supported_models:
|
|
63
|
+
return
|
|
64
|
+
supported = ", ".join(supported_models)
|
|
65
|
+
raise UnsupportedTaskInputError(
|
|
66
|
+
kind,
|
|
67
|
+
"metadata.model",
|
|
68
|
+
f"model {model!r} is not supported by this runtime; supported: {supported}",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def metadata_str(metadata_values: Mapping[str, Any], key: str) -> str | None:
|
|
73
|
+
"""Return a stripped string metadata value."""
|
|
74
|
+
|
|
75
|
+
value = metadata_values.get(key)
|
|
76
|
+
if not isinstance(value, str):
|
|
77
|
+
return None
|
|
78
|
+
stripped = value.strip()
|
|
79
|
+
return stripped or None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def output_schema_from(
|
|
83
|
+
task_output_schema: Mapping[str, Any] | None,
|
|
84
|
+
metadata_values: Mapping[str, Any],
|
|
85
|
+
) -> Mapping[str, Any] | None:
|
|
86
|
+
"""Resolve output schema from first-class task field or metadata aliases."""
|
|
87
|
+
|
|
88
|
+
if task_output_schema is not None:
|
|
89
|
+
return task_output_schema
|
|
90
|
+
for key in ("output_schema", "json_schema"):
|
|
91
|
+
raw = metadata_values.get(key)
|
|
92
|
+
if isinstance(raw, Mapping):
|
|
93
|
+
return raw
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parse_json_output(output: str) -> Any | None:
|
|
98
|
+
"""Best-effort JSON parsing for structured-output fallbacks."""
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
return json.loads(output)
|
|
102
|
+
except json.JSONDecodeError:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def filter_supported_kwargs(factory: Any, kwargs: Mapping[str, Any]) -> dict[str, Any]:
|
|
107
|
+
"""Drop kwargs unsupported by an injected or vendor options constructor."""
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
signature = inspect.signature(factory)
|
|
111
|
+
except (TypeError, ValueError):
|
|
112
|
+
return dict(kwargs)
|
|
113
|
+
if any(param.kind is inspect.Parameter.VAR_KEYWORD for param in signature.parameters.values()):
|
|
114
|
+
return dict(kwargs)
|
|
115
|
+
return {key: value for key, value in kwargs.items() if key in signature.parameters}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _extra_name(kind: AgentRuntimeKind) -> str:
|
|
119
|
+
return {
|
|
120
|
+
AgentRuntimeKind.CLAUDE_AGENT_SDK: "claude",
|
|
121
|
+
AgentRuntimeKind.CODEX_AGENT_SDK: "codex",
|
|
122
|
+
AgentRuntimeKind.ANTIGRAVITY_AGENT_SDK: "antigravity",
|
|
123
|
+
}.get(kind, "all")
|