opencode-a2a 0.3.1__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.
- opencode_a2a/__init__.py +15 -0
- opencode_a2a/cli.py +52 -0
- opencode_a2a/config.py +160 -0
- opencode_a2a/contracts/__init__.py +1 -0
- opencode_a2a/contracts/extensions.py +948 -0
- opencode_a2a/execution/__init__.py +1 -0
- opencode_a2a/execution/executor.py +1582 -0
- opencode_a2a/execution/request_context.py +91 -0
- opencode_a2a/execution/stream_events.py +578 -0
- opencode_a2a/execution/stream_state.py +279 -0
- opencode_a2a/execution/upstream_errors.py +264 -0
- opencode_a2a/jsonrpc/__init__.py +1 -0
- opencode_a2a/jsonrpc/application.py +1036 -0
- opencode_a2a/jsonrpc/methods.py +537 -0
- opencode_a2a/jsonrpc/params.py +123 -0
- opencode_a2a/opencode_upstream_client.py +544 -0
- opencode_a2a/parts/__init__.py +1 -0
- opencode_a2a/parts/mapping.py +151 -0
- opencode_a2a/parts/text.py +24 -0
- opencode_a2a/profile/__init__.py +1 -0
- opencode_a2a/profile/runtime.py +254 -0
- opencode_a2a/server/__init__.py +1 -0
- opencode_a2a/server/agent_card.py +288 -0
- opencode_a2a/server/application.py +634 -0
- opencode_a2a/server/openapi.py +432 -0
- opencode_a2a/server/request_parsing.py +109 -0
- opencode_a2a-0.3.1.dist-info/METADATA +173 -0
- opencode_a2a-0.3.1.dist-info/RECORD +32 -0
- opencode_a2a-0.3.1.dist-info/WHEEL +5 -0
- opencode_a2a-0.3.1.dist-info/entry_points.txt +2 -0
- opencode_a2a-0.3.1.dist-info/licenses/LICENSE +176 -0
- opencode_a2a-0.3.1.dist-info/top_level.txt +1 -0
opencode_a2a/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""A2A wrapper for opencode."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
UNKNOWN_VERSION = "0+unknown"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_package_version() -> str:
|
|
9
|
+
try:
|
|
10
|
+
return version("opencode-a2a")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
return UNKNOWN_VERSION
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__version__ = get_package_version()
|
opencode_a2a/cli.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .server.application import main as serve_main
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
prog="opencode-a2a",
|
|
14
|
+
description=(
|
|
15
|
+
"OpenCode A2A runtime. Deployment supervision is intentionally left to the operator."
|
|
16
|
+
),
|
|
17
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--version",
|
|
21
|
+
action="version",
|
|
22
|
+
version=f"%(prog)s {__version__}",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
26
|
+
subparsers.add_parser(
|
|
27
|
+
"serve",
|
|
28
|
+
help="Start the OpenCode A2A runtime using environment-based settings.",
|
|
29
|
+
description="Start the OpenCode A2A runtime using environment-based settings.",
|
|
30
|
+
)
|
|
31
|
+
return parser
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
35
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
36
|
+
parser = build_parser()
|
|
37
|
+
|
|
38
|
+
if not args:
|
|
39
|
+
serve_main()
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
namespace = parser.parse_args(args)
|
|
43
|
+
if namespace.command in {None, "serve"}:
|
|
44
|
+
serve_main()
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
parser.error(f"Unknown command: {namespace.command}")
|
|
48
|
+
return 2
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
raise SystemExit(main())
|
opencode_a2a/config.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated, Any, Literal, cast
|
|
5
|
+
|
|
6
|
+
from pydantic import Field, field_validator
|
|
7
|
+
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
|
|
8
|
+
|
|
9
|
+
from opencode_a2a import __version__
|
|
10
|
+
|
|
11
|
+
SandboxMode = Literal[
|
|
12
|
+
"unknown",
|
|
13
|
+
"read-only",
|
|
14
|
+
"workspace-write",
|
|
15
|
+
"danger-full-access",
|
|
16
|
+
"custom",
|
|
17
|
+
]
|
|
18
|
+
SandboxFilesystemScope = Literal[
|
|
19
|
+
"unknown",
|
|
20
|
+
"workspace_only",
|
|
21
|
+
"workspace_and_declared_roots",
|
|
22
|
+
"unrestricted",
|
|
23
|
+
"custom",
|
|
24
|
+
]
|
|
25
|
+
NetworkAccess = Literal["unknown", "disabled", "enabled", "restricted", "custom"]
|
|
26
|
+
ApprovalPolicy = Literal["unknown", "never", "on-request", "on-failure", "untrusted", "custom"]
|
|
27
|
+
EscalationBehavior = Literal["unknown", "manual", "automatic", "unsupported", "custom"]
|
|
28
|
+
WriteAccessScope = Literal[
|
|
29
|
+
"unknown",
|
|
30
|
+
"none",
|
|
31
|
+
"workspace_only",
|
|
32
|
+
"workspace_and_declared_roots",
|
|
33
|
+
"unrestricted",
|
|
34
|
+
"custom",
|
|
35
|
+
]
|
|
36
|
+
OutsideWorkspaceAccess = Literal["unknown", "allowed", "disallowed", "custom"]
|
|
37
|
+
DeclaredStringList = Annotated[tuple[str, ...], NoDecode]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _parse_declared_list(value: Any) -> tuple[str, ...]:
|
|
41
|
+
if value is None:
|
|
42
|
+
return ()
|
|
43
|
+
if isinstance(value, str):
|
|
44
|
+
raw = value.strip()
|
|
45
|
+
if not raw:
|
|
46
|
+
return ()
|
|
47
|
+
if raw.startswith("["):
|
|
48
|
+
try:
|
|
49
|
+
parsed = json.loads(raw)
|
|
50
|
+
except json.JSONDecodeError:
|
|
51
|
+
parsed = None
|
|
52
|
+
else:
|
|
53
|
+
if not isinstance(parsed, list):
|
|
54
|
+
raise TypeError("Expected a JSON array for declared list values.")
|
|
55
|
+
return tuple(str(item).strip() for item in parsed if str(item).strip())
|
|
56
|
+
return tuple(item.strip() for item in raw.split(",") if item.strip())
|
|
57
|
+
if isinstance(value, (list, tuple)):
|
|
58
|
+
return tuple(str(item).strip() for item in value if str(item).strip())
|
|
59
|
+
raise TypeError("Expected a comma-separated string, JSON array, or sequence.")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Settings(BaseSettings):
|
|
63
|
+
model_config = SettingsConfigDict(
|
|
64
|
+
env_prefix="",
|
|
65
|
+
case_sensitive=False,
|
|
66
|
+
env_file=".env",
|
|
67
|
+
extra="ignore",
|
|
68
|
+
populate_by_name=True,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# OpenCode settings
|
|
72
|
+
opencode_base_url: str = Field(default="http://127.0.0.1:4096", alias="OPENCODE_BASE_URL")
|
|
73
|
+
opencode_workspace_root: str | None = Field(default=None, alias="OPENCODE_WORKSPACE_ROOT")
|
|
74
|
+
opencode_agent: str | None = Field(default=None, alias="OPENCODE_AGENT")
|
|
75
|
+
opencode_system: str | None = Field(default=None, alias="OPENCODE_SYSTEM")
|
|
76
|
+
opencode_variant: str | None = Field(default=None, alias="OPENCODE_VARIANT")
|
|
77
|
+
opencode_timeout: float = Field(default=120.0, alias="OPENCODE_TIMEOUT")
|
|
78
|
+
opencode_timeout_stream: float | None = Field(default=None, alias="OPENCODE_TIMEOUT_STREAM")
|
|
79
|
+
|
|
80
|
+
# A2A settings
|
|
81
|
+
a2a_public_url: str = Field(default="http://127.0.0.1:8000", alias="A2A_PUBLIC_URL")
|
|
82
|
+
a2a_project: str | None = Field(default=None, alias="A2A_PROJECT")
|
|
83
|
+
a2a_title: str = Field(default="OpenCode A2A", alias="A2A_TITLE")
|
|
84
|
+
a2a_description: str = Field(default="OpenCode A2A runtime", alias="A2A_DESCRIPTION")
|
|
85
|
+
a2a_version: str = Field(default=__version__, alias="A2A_VERSION")
|
|
86
|
+
a2a_protocol_version: str = Field(default="0.3.0", alias="A2A_PROTOCOL_VERSION")
|
|
87
|
+
a2a_log_level: str = Field(default="WARNING", alias="A2A_LOG_LEVEL")
|
|
88
|
+
a2a_log_payloads: bool = Field(default=False, alias="A2A_LOG_PAYLOADS")
|
|
89
|
+
a2a_log_body_limit: int = Field(default=0, alias="A2A_LOG_BODY_LIMIT")
|
|
90
|
+
a2a_max_request_body_bytes: int = Field(
|
|
91
|
+
default=1_048_576,
|
|
92
|
+
ge=0,
|
|
93
|
+
alias="A2A_MAX_REQUEST_BODY_BYTES",
|
|
94
|
+
)
|
|
95
|
+
a2a_documentation_url: str | None = Field(default=None, alias="A2A_DOCUMENTATION_URL")
|
|
96
|
+
a2a_allow_directory_override: bool = Field(default=True, alias="A2A_ALLOW_DIRECTORY_OVERRIDE")
|
|
97
|
+
a2a_enable_session_shell: bool = Field(default=False, alias="A2A_ENABLE_SESSION_SHELL")
|
|
98
|
+
a2a_sandbox_mode: SandboxMode = Field(default="unknown", alias="A2A_SANDBOX_MODE")
|
|
99
|
+
a2a_sandbox_filesystem_scope: SandboxFilesystemScope = Field(
|
|
100
|
+
default="unknown",
|
|
101
|
+
alias="A2A_SANDBOX_FILESYSTEM_SCOPE",
|
|
102
|
+
)
|
|
103
|
+
a2a_sandbox_writable_roots: DeclaredStringList = Field(
|
|
104
|
+
default=(),
|
|
105
|
+
alias="A2A_SANDBOX_WRITABLE_ROOTS",
|
|
106
|
+
)
|
|
107
|
+
a2a_network_access: NetworkAccess = Field(default="unknown", alias="A2A_NETWORK_ACCESS")
|
|
108
|
+
a2a_network_allowed_domains: DeclaredStringList = Field(
|
|
109
|
+
default=(),
|
|
110
|
+
alias="A2A_NETWORK_ALLOWED_DOMAINS",
|
|
111
|
+
)
|
|
112
|
+
a2a_approval_policy: ApprovalPolicy = Field(
|
|
113
|
+
default="unknown",
|
|
114
|
+
alias="A2A_APPROVAL_POLICY",
|
|
115
|
+
)
|
|
116
|
+
a2a_approval_escalation_behavior: EscalationBehavior = Field(
|
|
117
|
+
default="unknown",
|
|
118
|
+
alias="A2A_APPROVAL_ESCALATION_BEHAVIOR",
|
|
119
|
+
)
|
|
120
|
+
a2a_write_access_scope: WriteAccessScope = Field(
|
|
121
|
+
default="unknown",
|
|
122
|
+
alias="A2A_WRITE_ACCESS_SCOPE",
|
|
123
|
+
)
|
|
124
|
+
a2a_write_access_outside_workspace: OutsideWorkspaceAccess = Field(
|
|
125
|
+
default="unknown",
|
|
126
|
+
alias="A2A_WRITE_ACCESS_OUTSIDE_WORKSPACE",
|
|
127
|
+
)
|
|
128
|
+
a2a_host: str = Field(default="127.0.0.1", alias="A2A_HOST")
|
|
129
|
+
a2a_port: int = Field(default=8000, alias="A2A_PORT")
|
|
130
|
+
a2a_bearer_token: str = Field(..., min_length=1, alias="A2A_BEARER_TOKEN")
|
|
131
|
+
|
|
132
|
+
# Session cache settings
|
|
133
|
+
a2a_session_cache_ttl_seconds: int = Field(default=3600, alias="A2A_SESSION_CACHE_TTL_SECONDS")
|
|
134
|
+
a2a_session_cache_maxsize: int = Field(default=10_000, alias="A2A_SESSION_CACHE_MAXSIZE")
|
|
135
|
+
a2a_interrupt_request_ttl_seconds: float = Field(
|
|
136
|
+
default=10_800.0,
|
|
137
|
+
ge=0.0,
|
|
138
|
+
alias="A2A_INTERRUPT_REQUEST_TTL_SECONDS",
|
|
139
|
+
)
|
|
140
|
+
a2a_interrupt_request_tombstone_ttl_seconds: float = Field(
|
|
141
|
+
default=600.0,
|
|
142
|
+
ge=0.0,
|
|
143
|
+
alias="A2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS",
|
|
144
|
+
)
|
|
145
|
+
a2a_cancel_abort_timeout_seconds: float = Field(
|
|
146
|
+
default=2.0,
|
|
147
|
+
ge=0.0,
|
|
148
|
+
alias="A2A_CANCEL_ABORT_TIMEOUT_SECONDS",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@field_validator("a2a_sandbox_writable_roots", "a2a_network_allowed_domains", mode="before")
|
|
152
|
+
@classmethod
|
|
153
|
+
def _normalize_declared_lists(cls, value: Any) -> tuple[str, ...]:
|
|
154
|
+
return _parse_declared_list(value)
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def from_env(cls) -> Settings:
|
|
158
|
+
# BaseSettings constructor loads values from env and applies validation.
|
|
159
|
+
settings_cls: type[BaseSettings] = cls
|
|
160
|
+
return cast(Settings, settings_cls())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Contract declarations for the A2A adapter surface."""
|