a2claude 0.1.0__tar.gz

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.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: a2claude
3
+ Version: 0.1.0
4
+ Summary: Run Claude Code as an A2A protocol agent server.
5
+ Keywords: a2a,claude-code,agent,agent2agent
6
+ Author: kanywst
7
+ License-Expression: Apache-2.0
8
+ Requires-Dist: a2a-sdk[http-server]>=1.1,<2
9
+ Requires-Dist: claude-agent-sdk>=0.2.101
10
+ Requires-Dist: uvicorn>=0.49
11
+ Requires-Dist: httpx>=0.28
12
+ Requires-Dist: typer>=0.26
13
+ Requires-Python: >=3.13
14
+ Project-URL: Repository, https://github.com/kanywst/a2claude
15
+ Description-Content-Type: text/markdown
16
+
17
+ <img src="assets/mascot.png" alt="a2claude" width="150" align="right">
18
+
19
+ # a2claude
20
+
21
+ Run Claude Code as an [A2A](https://a2aprotocol.ai/) agent server. Other agents
22
+ call it over the protocol; it drives a real Claude Code session in your project
23
+ and streams back the actual work — the tools it runs, the diffs it writes, what
24
+ it costs, and the permissions it needs.
25
+
26
+ [![CI](https://github.com/kanywst/a2claude/actions/workflows/ci.yml/badge.svg)](https://github.com/kanywst/a2claude/actions/workflows/ci.yml)
27
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
28
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/)
29
+ [![Protocol: A2A 1.0](https://img.shields.io/badge/protocol-A2A%201.0-D97757.svg)](https://a2aprotocol.ai/)
30
+
31
+ ![a2claude streaming a task, then pausing on a permission prompt](assets/demo.gif)
32
+
33
+ Most "wrap a coding agent in A2A" adapters flatten everything to text in, text
34
+ out. a2claude keeps the parts that matter for the agent on the other end: which
35
+ tools ran, what files changed, what it cost, and how to continue the same
36
+ session on the next turn.
37
+
38
+ ## How it maps to A2A
39
+
40
+ | Claude Code produces | A2A surface it lands on |
41
+ | --- | --- |
42
+ | Assistant text | A streamed artifact (`append` / `last_chunk`) |
43
+ | A tool call (Bash, Edit) | A `working` status update for the action |
44
+ | A file edit | A named artifact carrying the diff |
45
+ | Run result | Cost, turns, and usage on the completion message |
46
+ | Session id | Mapped to the A2A `contextId` so follow-ups resume |
47
+
48
+ The mapping lives in one place (`executor.py`). Backends only emit
49
+ normalized events; they never touch the protocol.
50
+
51
+ ## Requirements
52
+
53
+ - Python 3.13+
54
+ - [uv](https://docs.astral.sh/uv/)
55
+ - Claude Code CLI on `PATH` (only for the `claude` backend)
56
+
57
+ ## Quick start
58
+
59
+ Install:
60
+
61
+ ```bash
62
+ uv sync
63
+ ```
64
+
65
+ The `echo` backend needs no API key and no Claude install, so you can
66
+ exercise the whole path offline first:
67
+
68
+ ```bash
69
+ uv run a2claude serve --backend echo &
70
+ # once the "Uvicorn running" line appears:
71
+ uv run a2claude call "fix the failing test"
72
+ ```
73
+
74
+ ```text
75
+ task 189b1c63-1a7b-4908-87c4-c8f3bba8f6b5
76
+ context 0b2a901e-2b6f-4c56-bba2-d0da546936e9
77
+
78
+ · Echo
79
+ fix the failing test
80
+ [completed] $0.0 · 1 turns
81
+ ```
82
+
83
+ Then point it at a real project:
84
+
85
+ ```bash
86
+ uv run a2claude serve --backend claude --cwd /path/to/project
87
+ uv run a2claude call "add a /health endpoint" --url http://localhost:9100/
88
+ ```
89
+
90
+ Continue the same conversation by passing the `context` from a previous turn:
91
+
92
+ ```bash
93
+ uv run a2claude call "now add a test for it" --context <context-id>
94
+ ```
95
+
96
+ ## Commands
97
+
98
+ | Command | Description |
99
+ | --- | --- |
100
+ | `a2claude serve` | Start the A2A server |
101
+ | `a2claude call TEXT` | Send a message and print the streamed events |
102
+ | `a2claude card` | Fetch and print the agent card |
103
+
104
+ The agent card is served at `/.well-known/agent-card.json` and advertises
105
+ Claude Code's abilities as discrete skills (generation, refactor, debug,
106
+ review, test, explain).
107
+
108
+ ## Backends
109
+
110
+ A backend turns a prompt into a stream of normalized events. Two ship today:
111
+
112
+ - `echo` — no dependencies; mirrors the input. For wiring checks and tests.
113
+ - `claude` — drives Claude Code through the Claude Agent SDK.
114
+
115
+ The split keeps the A2A layer independent of how Claude Code is invoked, so
116
+ a raw-CLI backend can be added later without touching the server or the
117
+ protocol mapping.
118
+
119
+ ## Authentication
120
+
121
+ The `claude` backend uses whatever the Claude CLI is configured with. When
122
+ the server answers on behalf of other agents, that has to be an Anthropic API
123
+ key (or Bedrock / Vertex) — Anthropic does not permit subscription
124
+ credentials for third-party serving. Set a per-run cost ceiling with
125
+ `--max-budget-usd`.
126
+
127
+ ## Permissions
128
+
129
+ A tool that needs approval pauses the task in the A2A `input-required` state
130
+ instead of being skipped. The caller answers with a follow-up message on the
131
+ same task:
132
+
133
+ ```bash
134
+ uv run a2claude call "sudo reboot"
135
+ # ... [input-required] Permission requested for Bash: $ sudo reboot
136
+ # reply: a2claude call "allow" --task <id> --context <id>
137
+ uv run a2claude call "allow" --task <id> --context <id>
138
+ ```
139
+
140
+ `allow` (or `yes`, `approve`, `ok`) approves; anything else denies. The Claude
141
+ session stays alive across the pause, so it resumes exactly where it stopped.
142
+
143
+ The server does not inherit your personal Claude settings, so it has no
144
+ pre-approved tool allowlist — every tool that needs approval routes through the
145
+ caller. Read-only actions Claude already treats as safe still run without a
146
+ prompt.
147
+
148
+ ## Long-running tasks
149
+
150
+ The agent card advertises push notifications. A caller can register a webhook
151
+ for a task and receive status and artifact updates by HTTP POST instead of
152
+ holding a stream open — useful when a run takes minutes. Streaming and polling
153
+ (`tasks/get`) both work too.
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ uv sync --dev
159
+ uv run ruff check src tests
160
+ uv run ruff format src tests
161
+ uv run mypy
162
+ uv run pytest
163
+ ```
164
+
165
+ CI runs these on Python 3.13 and 3.14, plus a Markdown lint, on every push and
166
+ pull request.
167
+
168
+ ## Releasing
169
+
170
+ Pushing a `v*` tag builds the package, creates a GitHub release with the
171
+ artifacts, and publishes to PyPI via trusted publishing:
172
+
173
+ ```bash
174
+ git tag v0.1.0
175
+ git push origin v0.1.0
176
+ ```
177
+
178
+ ## Status
179
+
180
+ The mapping is complete end to end and verified against real Claude: text round
181
+ trip, tool-progress updates, streaming artifacts, file diffs as artifacts, run
182
+ metadata, session continuity, the permission → `input-required` round trip, and
183
+ push notifications. The offline `echo` backend covers every path including
184
+ permissions, so it can all be exercised without an API key.
185
+
186
+ ## License
187
+
188
+ Apache 2.0 — see [LICENSE](LICENSE).
@@ -0,0 +1,172 @@
1
+ <img src="assets/mascot.png" alt="a2claude" width="150" align="right">
2
+
3
+ # a2claude
4
+
5
+ Run Claude Code as an [A2A](https://a2aprotocol.ai/) agent server. Other agents
6
+ call it over the protocol; it drives a real Claude Code session in your project
7
+ and streams back the actual work — the tools it runs, the diffs it writes, what
8
+ it costs, and the permissions it needs.
9
+
10
+ [![CI](https://github.com/kanywst/a2claude/actions/workflows/ci.yml/badge.svg)](https://github.com/kanywst/a2claude/actions/workflows/ci.yml)
11
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
12
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/)
13
+ [![Protocol: A2A 1.0](https://img.shields.io/badge/protocol-A2A%201.0-D97757.svg)](https://a2aprotocol.ai/)
14
+
15
+ ![a2claude streaming a task, then pausing on a permission prompt](assets/demo.gif)
16
+
17
+ Most "wrap a coding agent in A2A" adapters flatten everything to text in, text
18
+ out. a2claude keeps the parts that matter for the agent on the other end: which
19
+ tools ran, what files changed, what it cost, and how to continue the same
20
+ session on the next turn.
21
+
22
+ ## How it maps to A2A
23
+
24
+ | Claude Code produces | A2A surface it lands on |
25
+ | --- | --- |
26
+ | Assistant text | A streamed artifact (`append` / `last_chunk`) |
27
+ | A tool call (Bash, Edit) | A `working` status update for the action |
28
+ | A file edit | A named artifact carrying the diff |
29
+ | Run result | Cost, turns, and usage on the completion message |
30
+ | Session id | Mapped to the A2A `contextId` so follow-ups resume |
31
+
32
+ The mapping lives in one place (`executor.py`). Backends only emit
33
+ normalized events; they never touch the protocol.
34
+
35
+ ## Requirements
36
+
37
+ - Python 3.13+
38
+ - [uv](https://docs.astral.sh/uv/)
39
+ - Claude Code CLI on `PATH` (only for the `claude` backend)
40
+
41
+ ## Quick start
42
+
43
+ Install:
44
+
45
+ ```bash
46
+ uv sync
47
+ ```
48
+
49
+ The `echo` backend needs no API key and no Claude install, so you can
50
+ exercise the whole path offline first:
51
+
52
+ ```bash
53
+ uv run a2claude serve --backend echo &
54
+ # once the "Uvicorn running" line appears:
55
+ uv run a2claude call "fix the failing test"
56
+ ```
57
+
58
+ ```text
59
+ task 189b1c63-1a7b-4908-87c4-c8f3bba8f6b5
60
+ context 0b2a901e-2b6f-4c56-bba2-d0da546936e9
61
+
62
+ · Echo
63
+ fix the failing test
64
+ [completed] $0.0 · 1 turns
65
+ ```
66
+
67
+ Then point it at a real project:
68
+
69
+ ```bash
70
+ uv run a2claude serve --backend claude --cwd /path/to/project
71
+ uv run a2claude call "add a /health endpoint" --url http://localhost:9100/
72
+ ```
73
+
74
+ Continue the same conversation by passing the `context` from a previous turn:
75
+
76
+ ```bash
77
+ uv run a2claude call "now add a test for it" --context <context-id>
78
+ ```
79
+
80
+ ## Commands
81
+
82
+ | Command | Description |
83
+ | --- | --- |
84
+ | `a2claude serve` | Start the A2A server |
85
+ | `a2claude call TEXT` | Send a message and print the streamed events |
86
+ | `a2claude card` | Fetch and print the agent card |
87
+
88
+ The agent card is served at `/.well-known/agent-card.json` and advertises
89
+ Claude Code's abilities as discrete skills (generation, refactor, debug,
90
+ review, test, explain).
91
+
92
+ ## Backends
93
+
94
+ A backend turns a prompt into a stream of normalized events. Two ship today:
95
+
96
+ - `echo` — no dependencies; mirrors the input. For wiring checks and tests.
97
+ - `claude` — drives Claude Code through the Claude Agent SDK.
98
+
99
+ The split keeps the A2A layer independent of how Claude Code is invoked, so
100
+ a raw-CLI backend can be added later without touching the server or the
101
+ protocol mapping.
102
+
103
+ ## Authentication
104
+
105
+ The `claude` backend uses whatever the Claude CLI is configured with. When
106
+ the server answers on behalf of other agents, that has to be an Anthropic API
107
+ key (or Bedrock / Vertex) — Anthropic does not permit subscription
108
+ credentials for third-party serving. Set a per-run cost ceiling with
109
+ `--max-budget-usd`.
110
+
111
+ ## Permissions
112
+
113
+ A tool that needs approval pauses the task in the A2A `input-required` state
114
+ instead of being skipped. The caller answers with a follow-up message on the
115
+ same task:
116
+
117
+ ```bash
118
+ uv run a2claude call "sudo reboot"
119
+ # ... [input-required] Permission requested for Bash: $ sudo reboot
120
+ # reply: a2claude call "allow" --task <id> --context <id>
121
+ uv run a2claude call "allow" --task <id> --context <id>
122
+ ```
123
+
124
+ `allow` (or `yes`, `approve`, `ok`) approves; anything else denies. The Claude
125
+ session stays alive across the pause, so it resumes exactly where it stopped.
126
+
127
+ The server does not inherit your personal Claude settings, so it has no
128
+ pre-approved tool allowlist — every tool that needs approval routes through the
129
+ caller. Read-only actions Claude already treats as safe still run without a
130
+ prompt.
131
+
132
+ ## Long-running tasks
133
+
134
+ The agent card advertises push notifications. A caller can register a webhook
135
+ for a task and receive status and artifact updates by HTTP POST instead of
136
+ holding a stream open — useful when a run takes minutes. Streaming and polling
137
+ (`tasks/get`) both work too.
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ uv sync --dev
143
+ uv run ruff check src tests
144
+ uv run ruff format src tests
145
+ uv run mypy
146
+ uv run pytest
147
+ ```
148
+
149
+ CI runs these on Python 3.13 and 3.14, plus a Markdown lint, on every push and
150
+ pull request.
151
+
152
+ ## Releasing
153
+
154
+ Pushing a `v*` tag builds the package, creates a GitHub release with the
155
+ artifacts, and publishes to PyPI via trusted publishing:
156
+
157
+ ```bash
158
+ git tag v0.1.0
159
+ git push origin v0.1.0
160
+ ```
161
+
162
+ ## Status
163
+
164
+ The mapping is complete end to end and verified against real Claude: text round
165
+ trip, tool-progress updates, streaming artifacts, file diffs as artifacts, run
166
+ metadata, session continuity, the permission → `input-required` round trip, and
167
+ push notifications. The offline `echo` backend covers every path including
168
+ permissions, so it can all be exercised without an API key.
169
+
170
+ ## License
171
+
172
+ Apache 2.0 — see [LICENSE](LICENSE).
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "a2claude"
3
+ version = "0.1.0"
4
+ description = "Run Claude Code as an A2A protocol agent server."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "Apache-2.0"
8
+ authors = [{ name = "kanywst" }]
9
+ keywords = ["a2a", "claude-code", "agent", "agent2agent"]
10
+ dependencies = [
11
+ "a2a-sdk[http-server]>=1.1,<2",
12
+ "claude-agent-sdk>=0.2.101",
13
+ "uvicorn>=0.49",
14
+ "httpx>=0.28",
15
+ "typer>=0.26",
16
+ ]
17
+
18
+ [project.scripts]
19
+ a2claude = "a2claude.cli:app"
20
+
21
+ [project.urls]
22
+ Repository = "https://github.com/kanywst/a2claude"
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "ruff>=0.15",
27
+ "pytest>=9",
28
+ "pytest-asyncio>=1.4",
29
+ "mypy>=2.1",
30
+ ]
31
+
32
+ [build-system]
33
+ requires = ["uv_build>=0.11.21,<0.12.0"]
34
+ build-backend = "uv_build"
35
+
36
+ [tool.ruff]
37
+ line-length = 88
38
+ target-version = "py313"
39
+
40
+ [tool.ruff.lint]
41
+ select = ["E", "F", "I", "UP", "B", "SIM"]
42
+
43
+ [tool.pytest.ini_options]
44
+ asyncio_mode = "auto"
45
+ testpaths = ["tests"]
46
+
47
+ [tool.mypy]
48
+ files = ["src"]
49
+ python_version = "3.13"
50
+ ignore_missing_imports = true
51
+ check_untyped_defs = true
@@ -0,0 +1,9 @@
1
+ """Run Claude Code as an A2A protocol agent server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .card import build_card
6
+ from .executor import ClaudeCodeExecutor
7
+ from .server import build_app
8
+
9
+ __all__ = ["build_app", "build_card", "ClaudeCodeExecutor"]
@@ -0,0 +1,47 @@
1
+ """Backends drive Claude Code and emit normalized events."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base import (
6
+ Backend,
7
+ BackendEvent,
8
+ FileChange,
9
+ PermissionDecision,
10
+ PermissionRequest,
11
+ Result,
12
+ RunRequest,
13
+ TextDelta,
14
+ ToolUse,
15
+ )
16
+ from .echo import EchoBackend
17
+ from .session import BackendSession
18
+
19
+ __all__ = [
20
+ "Backend",
21
+ "BackendEvent",
22
+ "BackendSession",
23
+ "FileChange",
24
+ "PermissionDecision",
25
+ "PermissionRequest",
26
+ "Result",
27
+ "RunRequest",
28
+ "TextDelta",
29
+ "ToolUse",
30
+ "EchoBackend",
31
+ "make_backend",
32
+ ]
33
+
34
+
35
+ def make_backend(name: str, **kwargs) -> Backend:
36
+ """Construct a backend by name.
37
+
38
+ ``claude`` is imported lazily so the echo backend works without the Claude
39
+ Agent SDK's runtime dependencies present.
40
+ """
41
+ if name == "echo":
42
+ return EchoBackend()
43
+ if name == "claude":
44
+ from .claude import ClaudeBackend
45
+
46
+ return ClaudeBackend(**kwargs)
47
+ raise ValueError(f"unknown backend: {name!r} (expected 'echo' or 'claude')")
@@ -0,0 +1,96 @@
1
+ """Backend abstraction.
2
+
3
+ A backend drives Claude Code and yields a normalized stream of events. The
4
+ A2A layer never imports the Claude Agent SDK directly — it only consumes these
5
+ events. That keeps the protocol mapping in one place and lets us swap the
6
+ underlying driver (Agent SDK today, raw CLI later) without touching the server.
7
+
8
+ Backends implement ``drive(session, request)``: they push events onto the
9
+ session and, when a tool needs approval, call ``session.request_permission(...)``
10
+ which parks until the A2A caller responds. This is what lets a permission prompt
11
+ become an A2A ``input-required`` round trip rather than being silently skipped.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
18
+
19
+ if TYPE_CHECKING:
20
+ from .session import BackendSession
21
+
22
+
23
+ @dataclass(slots=True)
24
+ class TextDelta:
25
+ """A chunk of assistant-authored text."""
26
+
27
+ text: str
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class ToolUse:
32
+ """The agent decided to run a tool (Bash, Edit, Read, ...)."""
33
+
34
+ name: str
35
+ tool_input: dict[str, Any]
36
+ tool_use_id: str
37
+
38
+
39
+ @dataclass(slots=True)
40
+ class FileChange:
41
+ """A file was written or edited during the run."""
42
+
43
+ path: str
44
+ diff: str
45
+
46
+
47
+ @dataclass(slots=True)
48
+ class PermissionRequest:
49
+ """A tool needs the caller's approval before it can run."""
50
+
51
+ request_id: str
52
+ tool_name: str
53
+ tool_input: dict[str, Any]
54
+ description: str = ""
55
+
56
+
57
+ @dataclass(slots=True)
58
+ class PermissionDecision:
59
+ """The caller's answer to a PermissionRequest."""
60
+
61
+ request_id: str
62
+ allow: bool
63
+ message: str = ""
64
+
65
+
66
+ @dataclass(slots=True)
67
+ class Result:
68
+ """Terminal event carrying run metadata."""
69
+
70
+ session_id: str | None = None
71
+ cost_usd: float | None = None
72
+ num_turns: int | None = None
73
+ usage: dict[str, Any] | None = None
74
+
75
+
76
+ BackendEvent = TextDelta | ToolUse | FileChange | PermissionRequest | Result
77
+
78
+
79
+ @dataclass(slots=True)
80
+ class RunRequest:
81
+ """One turn of work handed to a backend."""
82
+
83
+ prompt: str
84
+ context_id: str | None = None
85
+ resume: str | None = None
86
+
87
+
88
+ @runtime_checkable
89
+ class Backend(Protocol):
90
+ """Anything that can drive Claude Code and emit normalized events."""
91
+
92
+ name: str
93
+
94
+ async def drive(self, session: BackendSession, request: RunRequest) -> None:
95
+ """Run one turn, emitting events onto ``session`` until it returns."""
96
+ ...
@@ -0,0 +1,125 @@
1
+ """Claude backend.
2
+
3
+ Drives Claude Code through the Claude Agent SDK's bidirectional client and
4
+ normalizes its typed message stream into backend events. Tool calls, file edits,
5
+ run cost, and the session id — everything the "text in, text out" wrappers
6
+ discard — are preserved for the A2A layer to map onto the protocol.
7
+
8
+ Permission prompts are routed through ``can_use_tool`` into the session's
9
+ ``request_permission``, so the caller approves or denies a tool over A2A instead
10
+ of the server skipping it.
11
+
12
+ Authentication follows whatever the Claude CLI is configured with. For a server
13
+ that answers on behalf of other agents that means an Anthropic API key (or
14
+ Bedrock/Vertex); subscription credentials are not permitted for third-party
15
+ serving.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ from collections.abc import Iterator
22
+
23
+ from claude_agent_sdk import (
24
+ AssistantMessage,
25
+ ClaudeAgentOptions,
26
+ ClaudeSDKClient,
27
+ PermissionMode,
28
+ PermissionResultAllow,
29
+ PermissionResultDeny,
30
+ ResultMessage,
31
+ SettingSource,
32
+ TextBlock,
33
+ ToolUseBlock,
34
+ )
35
+
36
+ from .base import BackendEvent, Result, RunRequest, TextDelta, ToolUse
37
+ from .diff import file_changes
38
+ from .session import BackendSession
39
+
40
+
41
+ def events_from_message(message: object) -> Iterator[BackendEvent]:
42
+ """Map one Claude Agent SDK message to normalized backend events.
43
+
44
+ Pure and side-effect free so the translation can be unit tested without a
45
+ live Claude session.
46
+ """
47
+ if isinstance(message, AssistantMessage):
48
+ for block in message.content:
49
+ if isinstance(block, TextBlock):
50
+ if block.text:
51
+ yield TextDelta(text=block.text)
52
+ elif isinstance(block, ToolUseBlock):
53
+ tool_input = dict(block.input or {})
54
+ yield ToolUse(block.name, tool_input, block.id)
55
+ yield from file_changes(block.name, tool_input)
56
+ elif isinstance(message, ResultMessage):
57
+ yield Result(
58
+ session_id=message.session_id,
59
+ cost_usd=message.total_cost_usd,
60
+ num_turns=message.num_turns,
61
+ usage=message.usage,
62
+ )
63
+
64
+
65
+ class ClaudeBackend:
66
+ name = "claude"
67
+
68
+ def __init__(
69
+ self,
70
+ *,
71
+ cwd: str | None = None,
72
+ allowed_tools: list[str] | None = None,
73
+ permission_mode: PermissionMode | None = None,
74
+ model: str | None = None,
75
+ max_budget_usd: float | None = None,
76
+ setting_sources: list[SettingSource] | None = None,
77
+ ) -> None:
78
+ self.cwd = os.path.abspath(cwd or os.getcwd())
79
+ self.allowed_tools = allowed_tools
80
+ self.permission_mode = permission_mode
81
+ self.model = model
82
+ self.max_budget_usd = max_budget_usd
83
+ # A server should not inherit a developer's personal tool allowlist:
84
+ # default to loading no settings so every tool routes through the A2A
85
+ # permission round trip. Pass e.g. ["project"] to opt back in.
86
+ self.setting_sources: list[SettingSource] = (
87
+ [] if setting_sources is None else setting_sources
88
+ )
89
+
90
+ def _options(self, request: RunRequest, can_use_tool) -> ClaudeAgentOptions:
91
+ options = ClaudeAgentOptions(
92
+ cwd=self.cwd,
93
+ can_use_tool=can_use_tool,
94
+ setting_sources=self.setting_sources,
95
+ )
96
+ if request.resume:
97
+ options.resume = request.resume
98
+ if self.allowed_tools:
99
+ options.allowed_tools = self.allowed_tools
100
+ if self.permission_mode:
101
+ options.permission_mode = self.permission_mode
102
+ if self.model:
103
+ options.model = self.model
104
+ if self.max_budget_usd is not None:
105
+ options.max_budget_usd = self.max_budget_usd
106
+ return options
107
+
108
+ async def drive(self, session: BackendSession, request: RunRequest) -> None:
109
+ async def can_use_tool(tool_name, tool_input, context):
110
+ description = getattr(context, "display_name", "") or tool_name
111
+ decision = await session.request_permission(
112
+ tool_name, dict(tool_input or {}), description
113
+ )
114
+ if decision.allow:
115
+ return PermissionResultAllow()
116
+ return PermissionResultDeny(
117
+ message=decision.message or "Denied by A2A caller"
118
+ )
119
+
120
+ options = self._options(request, can_use_tool)
121
+ async with ClaudeSDKClient(options=options) as client:
122
+ await client.query(request.prompt)
123
+ async for message in client.receive_response():
124
+ for event in events_from_message(message):
125
+ await session.emit(event)