codex-a2a 0.3.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.
Files changed (57) hide show
  1. codex_a2a/__init__.py +34 -0
  2. codex_a2a/cli.py +38 -0
  3. codex_a2a/config.py +284 -0
  4. codex_a2a/contracts/__init__.py +0 -0
  5. codex_a2a/contracts/extensions.py +720 -0
  6. codex_a2a/contracts/runtime_output.py +264 -0
  7. codex_a2a/execution/__init__.py +0 -0
  8. codex_a2a/execution/cancellation.py +82 -0
  9. codex_a2a/execution/directory_policy.py +44 -0
  10. codex_a2a/execution/executor.py +451 -0
  11. codex_a2a/execution/output_mapping.py +217 -0
  12. codex_a2a/execution/request_metadata.py +60 -0
  13. codex_a2a/execution/response_emitter.py +108 -0
  14. codex_a2a/execution/session_runtime.py +314 -0
  15. codex_a2a/execution/stream_chunks.py +413 -0
  16. codex_a2a/execution/stream_interrupts.py +225 -0
  17. codex_a2a/execution/stream_processor.py +501 -0
  18. codex_a2a/execution/stream_state.py +232 -0
  19. codex_a2a/execution/streaming.py +153 -0
  20. codex_a2a/execution/tool_call_payloads.py +338 -0
  21. codex_a2a/jsonrpc/__init__.py +0 -0
  22. codex_a2a/jsonrpc/application.py +183 -0
  23. codex_a2a/jsonrpc/control_params.py +227 -0
  24. codex_a2a/jsonrpc/errors.py +95 -0
  25. codex_a2a/jsonrpc/interrupt_lifecycle.py +120 -0
  26. codex_a2a/jsonrpc/interrupt_params.py +165 -0
  27. codex_a2a/jsonrpc/interrupts.py +164 -0
  28. codex_a2a/jsonrpc/params.py +41 -0
  29. codex_a2a/jsonrpc/params_common.py +131 -0
  30. codex_a2a/jsonrpc/payload_mapping.py +97 -0
  31. codex_a2a/jsonrpc/query_params.py +168 -0
  32. codex_a2a/jsonrpc/session_control.py +167 -0
  33. codex_a2a/jsonrpc/session_query.py +119 -0
  34. codex_a2a/logging_context.py +63 -0
  35. codex_a2a/metrics.py +72 -0
  36. codex_a2a/parts/text.py +15 -0
  37. codex_a2a/profile/__init__.py +0 -0
  38. codex_a2a/profile/runtime.py +346 -0
  39. codex_a2a/server/__init__.py +0 -0
  40. codex_a2a/server/agent_card.py +228 -0
  41. codex_a2a/server/application.py +163 -0
  42. codex_a2a/server/call_context.py +39 -0
  43. codex_a2a/server/http_middlewares.py +299 -0
  44. codex_a2a/server/openapi.py +315 -0
  45. codex_a2a/server/request_handler.py +183 -0
  46. codex_a2a/upstream/__init__.py +11 -0
  47. codex_a2a/upstream/client.py +1075 -0
  48. codex_a2a/upstream/interrupts.py +168 -0
  49. codex_a2a/upstream/models.py +47 -0
  50. codex_a2a/upstream/notification_mapping.py +150 -0
  51. codex_a2a/upstream/request_mapping.py +57 -0
  52. codex_a2a-0.3.0.dist-info/METADATA +189 -0
  53. codex_a2a-0.3.0.dist-info/RECORD +57 -0
  54. codex_a2a-0.3.0.dist-info/WHEEL +5 -0
  55. codex_a2a-0.3.0.dist-info/entry_points.txt +2 -0
  56. codex_a2a-0.3.0.dist-info/licenses/LICENSE +176 -0
  57. codex_a2a-0.3.0.dist-info/top_level.txt +1 -0
codex_a2a/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ """Codex A2A runtime package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ _PACKAGE_NAME = "codex-a2a"
8
+ _UNKNOWN_VERSION = "0.0.0+unknown"
9
+
10
+
11
+ def _package_version() -> str | None:
12
+ try:
13
+ return version(_PACKAGE_NAME)
14
+ except PackageNotFoundError:
15
+ return None
16
+
17
+
18
+ def _scm_version() -> str | None:
19
+ try:
20
+ from setuptools_scm import get_version
21
+ except ImportError:
22
+ return None
23
+
24
+ try:
25
+ return get_version(root="../..", relative_to=__file__)
26
+ except LookupError:
27
+ return None
28
+
29
+
30
+ def _resolve_version() -> str:
31
+ return _package_version() or _scm_version() or _UNKNOWN_VERSION
32
+
33
+
34
+ __version__ = _resolve_version()
codex_a2a/cli.py ADDED
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+ from collections.abc import Sequence
6
+
7
+ from . import __version__
8
+
9
+
10
+ def build_parser() -> argparse.ArgumentParser:
11
+ parser = argparse.ArgumentParser(
12
+ prog="codex-a2a",
13
+ description="Codex A2A CLI.",
14
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
15
+ )
16
+ parser.add_argument(
17
+ "--version",
18
+ action="version",
19
+ version=f"%(prog)s {__version__}",
20
+ )
21
+ return parser
22
+
23
+
24
+ def _serve_main() -> None:
25
+ from .server.application import main as serve_main
26
+
27
+ serve_main()
28
+
29
+
30
+ def main(argv: Sequence[str] | None = None) -> int:
31
+ parser = build_parser()
32
+ parser.parse_args(list(sys.argv[1:] if argv is None else argv))
33
+ _serve_main()
34
+ return 0
35
+
36
+
37
+ if __name__ == "__main__":
38
+ raise SystemExit(main())
codex_a2a/config.py ADDED
@@ -0,0 +1,284 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated, Any, cast
4
+
5
+ from pydantic import Field, field_validator
6
+ from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
7
+
8
+ from codex_a2a import __version__
9
+
10
+ _SANDBOX_MODES = {
11
+ "unknown",
12
+ "read-only",
13
+ "workspace-write",
14
+ "danger-full-access",
15
+ }
16
+ _FILESYSTEM_SCOPES = {
17
+ "unknown",
18
+ "none",
19
+ "workspace_root",
20
+ "workspace_root_or_descendant",
21
+ "configured_roots",
22
+ "full_filesystem",
23
+ }
24
+ _NETWORK_ACCESS_MODES = {
25
+ "unknown",
26
+ "disabled",
27
+ "enabled",
28
+ "restricted",
29
+ }
30
+ _APPROVAL_POLICIES = {
31
+ "unknown",
32
+ "never",
33
+ "on-request",
34
+ "on-failure",
35
+ "untrusted-only",
36
+ }
37
+ _APPROVAL_ESCALATION_BEHAVIORS = {
38
+ "unknown",
39
+ "unavailable",
40
+ "per_request",
41
+ "fallback_only",
42
+ "restricted",
43
+ }
44
+
45
+
46
+ def _parse_str_list(value: Any) -> Any:
47
+ if value is None:
48
+ return []
49
+ if isinstance(value, str):
50
+ stripped = value.strip()
51
+ if not stripped:
52
+ return []
53
+ return [item.strip() for item in stripped.split(",") if item.strip()]
54
+ if isinstance(value, tuple):
55
+ return list(value)
56
+ return value
57
+
58
+
59
+ def _validate_choice(value: str, *, allowed: set[str], env_name: str) -> str:
60
+ if value not in allowed:
61
+ allowed_values = ", ".join(sorted(allowed))
62
+ raise ValueError(f"{env_name} must be one of: {allowed_values}")
63
+ return value
64
+
65
+
66
+ class Settings(BaseSettings):
67
+ model_config = SettingsConfigDict(
68
+ env_prefix="",
69
+ case_sensitive=False,
70
+ env_file=".env",
71
+ extra="ignore",
72
+ populate_by_name=True,
73
+ )
74
+
75
+ # Codex settings (app-server mode)
76
+ codex_workspace_root: str | None = Field(
77
+ default=None,
78
+ alias="CODEX_WORKSPACE_ROOT",
79
+ )
80
+ codex_provider_id: str | None = Field(
81
+ default=None,
82
+ alias="CODEX_PROVIDER_ID",
83
+ )
84
+ codex_model_id: str | None = Field(
85
+ default=None,
86
+ alias="CODEX_MODEL_ID",
87
+ )
88
+ codex_agent: str | None = Field(
89
+ default=None,
90
+ alias="CODEX_AGENT",
91
+ )
92
+ codex_variant: str | None = Field(
93
+ default=None,
94
+ alias="CODEX_VARIANT",
95
+ )
96
+ codex_timeout: float = Field(
97
+ default=120.0,
98
+ alias="CODEX_TIMEOUT",
99
+ )
100
+ codex_timeout_stream: float | None = Field(
101
+ default=None,
102
+ alias="CODEX_TIMEOUT_STREAM",
103
+ )
104
+ codex_cli_bin: str = Field(
105
+ default="codex",
106
+ alias="CODEX_CLI_BIN",
107
+ )
108
+ codex_app_server_listen: str = Field(
109
+ default="stdio://",
110
+ alias="CODEX_APP_SERVER_LISTEN",
111
+ )
112
+ codex_model: str = Field(
113
+ default="gpt-5.1-codex",
114
+ alias="CODEX_MODEL",
115
+ )
116
+ codex_model_reasoning_effort: str | None = Field(
117
+ default=None,
118
+ alias="CODEX_MODEL_REASONING_EFFORT",
119
+ )
120
+
121
+ # A2A settings
122
+ a2a_public_url: str = Field(default="http://127.0.0.1:8000", alias="A2A_PUBLIC_URL")
123
+ a2a_project: str | None = Field(default=None, alias="A2A_PROJECT")
124
+ a2a_title: str = Field(default="Codex A2A", alias="A2A_TITLE")
125
+ a2a_description: str = Field(default="A2A wrapper service for Codex", alias="A2A_DESCRIPTION")
126
+ a2a_version: str = Field(default=__version__, alias="A2A_VERSION")
127
+ a2a_protocol_version: str = Field(default="0.3.0", alias="A2A_PROTOCOL_VERSION")
128
+ a2a_enable_health_endpoint: bool = Field(default=True, alias="A2A_ENABLE_HEALTH_ENDPOINT")
129
+ a2a_enable_session_shell: bool = Field(default=True, alias="A2A_ENABLE_SESSION_SHELL")
130
+ a2a_log_level: str = Field(default="INFO", alias="A2A_LOG_LEVEL")
131
+ a2a_log_payloads: bool = Field(default=False, alias="A2A_LOG_PAYLOADS")
132
+ a2a_log_body_limit: int = Field(default=0, alias="A2A_LOG_BODY_LIMIT")
133
+ a2a_documentation_url: str | None = Field(default=None, alias="A2A_DOCUMENTATION_URL")
134
+ a2a_allow_directory_override: bool = Field(default=True, alias="A2A_ALLOW_DIRECTORY_OVERRIDE")
135
+ a2a_host: str = Field(default="127.0.0.1", alias="A2A_HOST")
136
+ a2a_port: int = Field(default=8000, alias="A2A_PORT")
137
+ a2a_bearer_token: str = Field(..., min_length=1, alias="A2A_BEARER_TOKEN")
138
+
139
+ # Session cache settings
140
+ a2a_session_cache_ttl_seconds: int = Field(default=3600, alias="A2A_SESSION_CACHE_TTL_SECONDS")
141
+ a2a_session_cache_maxsize: int = Field(default=10_000, alias="A2A_SESSION_CACHE_MAXSIZE")
142
+ a2a_cancel_abort_timeout_seconds: float = Field(
143
+ default=1.0,
144
+ alias="A2A_CANCEL_ABORT_TIMEOUT_SECONDS",
145
+ )
146
+ a2a_stream_idle_diagnostic_seconds: float = Field(
147
+ default=60.0,
148
+ alias="A2A_STREAM_IDLE_DIAGNOSTIC_SECONDS",
149
+ )
150
+ a2a_interrupt_request_ttl_seconds: int = Field(
151
+ default=3600,
152
+ alias="A2A_INTERRUPT_REQUEST_TTL_SECONDS",
153
+ )
154
+ a2a_execution_sandbox_mode: str = Field(
155
+ default="unknown",
156
+ alias="A2A_EXECUTION_SANDBOX_MODE",
157
+ )
158
+ a2a_execution_sandbox_filesystem_scope: str | None = Field(
159
+ default=None,
160
+ alias="A2A_EXECUTION_SANDBOX_FILESYSTEM_SCOPE",
161
+ )
162
+ a2a_execution_sandbox_writable_roots: Annotated[list[str], NoDecode] = Field(
163
+ default_factory=list,
164
+ alias="A2A_EXECUTION_SANDBOX_WRITABLE_ROOTS",
165
+ )
166
+ a2a_execution_network_access: str = Field(
167
+ default="unknown",
168
+ alias="A2A_EXECUTION_NETWORK_ACCESS",
169
+ )
170
+ a2a_execution_network_allowed_domains: Annotated[list[str], NoDecode] = Field(
171
+ default_factory=list,
172
+ alias="A2A_EXECUTION_NETWORK_ALLOWED_DOMAINS",
173
+ )
174
+ a2a_execution_approval_policy: str = Field(
175
+ default="unknown",
176
+ alias="A2A_EXECUTION_APPROVAL_POLICY",
177
+ )
178
+ a2a_execution_approval_escalation_behavior: str | None = Field(
179
+ default=None,
180
+ alias="A2A_EXECUTION_APPROVAL_ESCALATION_BEHAVIOR",
181
+ )
182
+ a2a_execution_write_access_scope: str | None = Field(
183
+ default=None,
184
+ alias="A2A_EXECUTION_WRITE_ACCESS_SCOPE",
185
+ )
186
+ a2a_execution_write_outside_workspace: bool | None = Field(
187
+ default=None,
188
+ alias="A2A_EXECUTION_WRITE_OUTSIDE_WORKSPACE",
189
+ )
190
+
191
+ @field_validator("a2a_cancel_abort_timeout_seconds")
192
+ @classmethod
193
+ def validate_cancel_abort_timeout_seconds(cls, value: float) -> float:
194
+ if value < 0:
195
+ raise ValueError("A2A_CANCEL_ABORT_TIMEOUT_SECONDS must be >= 0")
196
+ return value
197
+
198
+ @field_validator("a2a_stream_idle_diagnostic_seconds")
199
+ @classmethod
200
+ def validate_stream_idle_diagnostic_seconds(cls, value: float) -> float:
201
+ if value <= 0:
202
+ raise ValueError("A2A_STREAM_IDLE_DIAGNOSTIC_SECONDS must be > 0")
203
+ return value
204
+
205
+ @field_validator("a2a_interrupt_request_ttl_seconds")
206
+ @classmethod
207
+ def validate_interrupt_request_ttl_seconds(cls, value: int) -> int:
208
+ if value < 1:
209
+ raise ValueError("A2A_INTERRUPT_REQUEST_TTL_SECONDS must be >= 1")
210
+ return value
211
+
212
+ @field_validator(
213
+ "a2a_execution_sandbox_writable_roots",
214
+ "a2a_execution_network_allowed_domains",
215
+ mode="before",
216
+ )
217
+ @classmethod
218
+ def parse_execution_lists(cls, value: Any) -> Any:
219
+ return _parse_str_list(value)
220
+
221
+ @field_validator("a2a_execution_sandbox_mode")
222
+ @classmethod
223
+ def validate_execution_sandbox_mode(cls, value: str) -> str:
224
+ return _validate_choice(
225
+ value,
226
+ allowed=_SANDBOX_MODES,
227
+ env_name="A2A_EXECUTION_SANDBOX_MODE",
228
+ )
229
+
230
+ @field_validator("a2a_execution_sandbox_filesystem_scope")
231
+ @classmethod
232
+ def validate_execution_sandbox_filesystem_scope(cls, value: str | None) -> str | None:
233
+ if value is None:
234
+ return value
235
+ return _validate_choice(
236
+ value,
237
+ allowed=_FILESYSTEM_SCOPES,
238
+ env_name="A2A_EXECUTION_SANDBOX_FILESYSTEM_SCOPE",
239
+ )
240
+
241
+ @field_validator("a2a_execution_network_access")
242
+ @classmethod
243
+ def validate_execution_network_access(cls, value: str) -> str:
244
+ return _validate_choice(
245
+ value,
246
+ allowed=_NETWORK_ACCESS_MODES,
247
+ env_name="A2A_EXECUTION_NETWORK_ACCESS",
248
+ )
249
+
250
+ @field_validator("a2a_execution_approval_policy")
251
+ @classmethod
252
+ def validate_execution_approval_policy(cls, value: str) -> str:
253
+ return _validate_choice(
254
+ value,
255
+ allowed=_APPROVAL_POLICIES,
256
+ env_name="A2A_EXECUTION_APPROVAL_POLICY",
257
+ )
258
+
259
+ @field_validator("a2a_execution_approval_escalation_behavior")
260
+ @classmethod
261
+ def validate_execution_approval_escalation_behavior(cls, value: str | None) -> str | None:
262
+ if value is None:
263
+ return value
264
+ return _validate_choice(
265
+ value,
266
+ allowed=_APPROVAL_ESCALATION_BEHAVIORS,
267
+ env_name="A2A_EXECUTION_APPROVAL_ESCALATION_BEHAVIOR",
268
+ )
269
+
270
+ @field_validator("a2a_execution_write_access_scope")
271
+ @classmethod
272
+ def validate_execution_write_access_scope(cls, value: str | None) -> str | None:
273
+ if value is None:
274
+ return value
275
+ return _validate_choice(
276
+ value,
277
+ allowed=_FILESYSTEM_SCOPES,
278
+ env_name="A2A_EXECUTION_WRITE_ACCESS_SCOPE",
279
+ )
280
+
281
+ @classmethod
282
+ def from_env(cls) -> Settings:
283
+ settings_cls: type[BaseSettings] = cls
284
+ return cast(Settings, settings_cls())
File without changes