agentshim 0.4.0__tar.gz → 0.5.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.
- agentshim-0.5.0/.github/workflows/ci.yml +41 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/PKG-INFO +88 -4
- {agentshim-0.4.0 → agentshim-0.5.0}/README.md +87 -3
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/__init__.py +16 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/base.py +7 -3
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/claude/agent.py +8 -5
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/cli_agent.py +44 -124
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/codex/agent.py +8 -5
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/copilot/agent.py +7 -5
- agentshim-0.5.0/agentshim/executor.py +319 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/gemini/agent.py +8 -5
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/opencode/agent.py +8 -5
- {agentshim-0.4.0 → agentshim-0.5.0}/pyproject.toml +1 -1
- agentshim-0.5.0/tests/unit/cli_agent/test_command_executor.py +245 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_coding_agent_facade.py +7 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/uv.lock +1 -1
- agentshim-0.4.0/.github/workflows/ci.yml +0 -18
- {agentshim-0.4.0 → agentshim-0.5.0}/.github/workflows/publish.yml +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/.gitignore +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/claude/__init__.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/claude/events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/claude/hooks/__init__.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/claude/hooks/confine_reads.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/claude_events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/codex/__init__.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/codex/events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/codex_events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/copilot/__init__.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/copilot/events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/copilot_events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/gemini/__init__.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/gemini/events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/gemini_events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/llm_client.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/mcp_config.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/opencode/__init__.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/opencode/events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/opencode_events.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/py.typed +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/sandbox.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/subagent.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/usage.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/agentshim/utils.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/fixtures/copilot/session_turn_1.jsonl +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/fixtures/copilot/session_turn_2_resumed.jsonl +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/fixtures/copilot/streaming_dedup.jsonl +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/fixtures/copilot/tool_and_usage.jsonl +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/cli_agent/test_agent_cli_cleanup.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/cli_agent/test_check_cli.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/cli_agent/test_cli_prompt_passing.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/llm/conftest.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/llm/test_claude_stream.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/llm/test_gemini_fixture.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/llm/test_gemini_stream.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_claude.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_codex.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_copilot.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_copilot_fixtures.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_event_parsing.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_mcp_unsupported.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_resume.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_agent_cli_sandbox.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_cli_agent_usage.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_event_handlers.py +0 -0
- {agentshim-0.4.0 → agentshim-0.5.0}/tests/unit/test_mcp_config.py +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v6
|
|
16
|
+
with:
|
|
17
|
+
enable-cache: true
|
|
18
|
+
cache-dependency-glob: uv.lock
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.11"
|
|
22
|
+
- run: uv sync --locked --dev
|
|
23
|
+
- run: uv run ruff check .
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
strategy:
|
|
28
|
+
fail-fast: false
|
|
29
|
+
matrix:
|
|
30
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v6
|
|
34
|
+
with:
|
|
35
|
+
enable-cache: true
|
|
36
|
+
cache-dependency-glob: uv.lock
|
|
37
|
+
- uses: actions/setup-python@v5
|
|
38
|
+
with:
|
|
39
|
+
python-version: ${{ matrix.python-version }}
|
|
40
|
+
- run: uv sync --locked --dev
|
|
41
|
+
- run: uv run pytest
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentshim
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Provider-agnostic coding-agent CLI shims
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: litellm>=1.0.0
|
|
@@ -22,6 +22,7 @@ prompting, session resumption, event parsing, or MCP configuration.
|
|
|
22
22
|
- a shared CLI agent abstraction with a provider registry
|
|
23
23
|
- adapters for Claude Code, Codex, Gemini, and Opencode
|
|
24
24
|
- stateful chat sessions that automatically resume provider-native threads
|
|
25
|
+
- injectable command executors for custom process launching or sandboxing
|
|
25
26
|
- MCP server config models for providers that support MCP
|
|
26
27
|
- sandbox settings helpers for Claude Code
|
|
27
28
|
- a lightweight LiteLLM client and subagent helper
|
|
@@ -152,7 +153,90 @@ agent = CodingAgent(provider="claude")
|
|
|
152
153
|
reply = agent.generate("Return only the answer.", silent=True)
|
|
153
154
|
```
|
|
154
155
|
|
|
155
|
-
### 3.
|
|
156
|
+
### 3. Run Commands Through a Custom Executor
|
|
157
|
+
|
|
158
|
+
Provider classes accept an optional `executor=`. The executor controls binary
|
|
159
|
+
lookup, CLI validation, and streaming process execution while `agentshim` keeps
|
|
160
|
+
owning provider command construction, stdout/stderr parsing, session state, and
|
|
161
|
+
event emission.
|
|
162
|
+
|
|
163
|
+
This is useful when a caller needs to run the CLI somewhere other than the
|
|
164
|
+
current host process, for example through an existing container, remote shell,
|
|
165
|
+
or custom sandbox.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from agentshim import (
|
|
169
|
+
CodexCodingAgent,
|
|
170
|
+
CommandHandle,
|
|
171
|
+
CommandRequest,
|
|
172
|
+
CommandResult,
|
|
173
|
+
CommandStreamSink,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class MyCommandHandle:
|
|
178
|
+
def terminate(self) -> None:
|
|
179
|
+
...
|
|
180
|
+
|
|
181
|
+
def kill(self) -> None:
|
|
182
|
+
...
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class MyExecutor:
|
|
186
|
+
def find_binary(self, binary_name: str, env: dict[str, str]) -> str:
|
|
187
|
+
# This value becomes request.argv[0]. Container or remote executors can
|
|
188
|
+
# return the binary name if lookup happens in the target runtime.
|
|
189
|
+
return binary_name
|
|
190
|
+
|
|
191
|
+
def check_binary(self, binary_path: str, env: dict[str, str], *, timeout: int) -> None:
|
|
192
|
+
# Raise RuntimeError if the target CLI is unavailable. No-op is fine
|
|
193
|
+
# when validation is not cheap or is handled by the runtime.
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
def run(self, request: CommandRequest, sink: CommandStreamSink) -> CommandResult:
|
|
197
|
+
handle = MyCommandHandle()
|
|
198
|
+
sink.started(handle)
|
|
199
|
+
|
|
200
|
+
stdout = ""
|
|
201
|
+
stderr = ""
|
|
202
|
+
|
|
203
|
+
# Run request.argv in your target runtime, with request.stdin,
|
|
204
|
+
# request.cwd, request.env, and request.timeout. Stream each complete
|
|
205
|
+
# line as it arrives, preserving trailing newlines when present.
|
|
206
|
+
line = "streamed output\n"
|
|
207
|
+
stdout += line
|
|
208
|
+
sink.stdout(line)
|
|
209
|
+
|
|
210
|
+
return CommandResult(returncode=0, stdout=stdout, stderr=stderr)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
agent = CodexCodingAgent(executor=MyExecutor())
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The default `HostCommandExecutor` preserves the normal local `subprocess`
|
|
217
|
+
behavior. `CodingAgent(provider=..., executor=...)` forwards the same executor
|
|
218
|
+
to the selected provider.
|
|
219
|
+
|
|
220
|
+
Executor contract:
|
|
221
|
+
|
|
222
|
+
- `CommandRequest.argv` is the complete provider CLI command. `argv[0]` is the
|
|
223
|
+
value returned by `find_binary`.
|
|
224
|
+
- `CommandRequest.stdin` is the prompt text to write to the command's standard
|
|
225
|
+
input, then stdin should be closed.
|
|
226
|
+
- `CommandRequest.cwd`, `env`, and `timeout` should be honored by the executor.
|
|
227
|
+
- Call `sink.started(handle)` once after the command starts. The handle only
|
|
228
|
+
needs `terminate()` and `kill()`.
|
|
229
|
+
- Call `sink.stdout(line)` and `sink.stderr(line)` as output is produced. Lines
|
|
230
|
+
should include trailing newlines when the underlying stream provided them.
|
|
231
|
+
- Return `CommandResult(returncode, stdout, stderr)` after the command exits.
|
|
232
|
+
The returned text should match what was streamed through the sink.
|
|
233
|
+
|
|
234
|
+
If you were using the pre-0.5 executor preview, replace
|
|
235
|
+
`run_streaming(cmd, ..., on_stdout, on_stderr, on_process_started)` with
|
|
236
|
+
`run(request, sink)`. The parser/event APIs remain internal; custom executors
|
|
237
|
+
only provide a stable command runtime.
|
|
238
|
+
|
|
239
|
+
### 4. Instantiate a Specific Provider Directly
|
|
156
240
|
|
|
157
241
|
If you already know which backend you want, construct the provider class
|
|
158
242
|
yourself.
|
|
@@ -174,7 +258,7 @@ The bundled provider classes are:
|
|
|
174
258
|
- `GeminiCodingAgent`
|
|
175
259
|
- `OpencodeCodingAgent`
|
|
176
260
|
|
|
177
|
-
###
|
|
261
|
+
### 5. Configure MCP Servers
|
|
178
262
|
|
|
179
263
|
Claude Code and Codex can be configured with MCP servers by passing
|
|
180
264
|
`HttpMcpServer` and `StdioMcpServer` objects at construction time.
|
|
@@ -251,7 +335,7 @@ Notes:
|
|
|
251
335
|
- Registration is import-driven. Your provider is available only after the module defining it has been imported in the current Python process.
|
|
252
336
|
- `list_providers()` returns canonical provider names only. Aliases resolve via `get_provider_class(...)` and `CodingAgent(provider=...)`.
|
|
253
337
|
- `register_provider(...)` rejects invalid names, abstract classes, and accidental name collisions unless you pass `overwrite=True`.
|
|
254
|
-
- If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `event_handler`, `event_handlers`, `mcp_servers`, and `
|
|
338
|
+
- If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `event_handler`, `event_handlers`, `mcp_servers`, `sandbox`, and `executor` as needed.
|
|
255
339
|
- If your provider needs extra constructor arguments beyond the shared portable set, pass them via `backend_kwargs={...}` when constructing `CodingAgent(...)`.
|
|
256
340
|
|
|
257
341
|
## Development
|
|
@@ -11,6 +11,7 @@ prompting, session resumption, event parsing, or MCP configuration.
|
|
|
11
11
|
- a shared CLI agent abstraction with a provider registry
|
|
12
12
|
- adapters for Claude Code, Codex, Gemini, and Opencode
|
|
13
13
|
- stateful chat sessions that automatically resume provider-native threads
|
|
14
|
+
- injectable command executors for custom process launching or sandboxing
|
|
14
15
|
- MCP server config models for providers that support MCP
|
|
15
16
|
- sandbox settings helpers for Claude Code
|
|
16
17
|
- a lightweight LiteLLM client and subagent helper
|
|
@@ -141,7 +142,90 @@ agent = CodingAgent(provider="claude")
|
|
|
141
142
|
reply = agent.generate("Return only the answer.", silent=True)
|
|
142
143
|
```
|
|
143
144
|
|
|
144
|
-
### 3.
|
|
145
|
+
### 3. Run Commands Through a Custom Executor
|
|
146
|
+
|
|
147
|
+
Provider classes accept an optional `executor=`. The executor controls binary
|
|
148
|
+
lookup, CLI validation, and streaming process execution while `agentshim` keeps
|
|
149
|
+
owning provider command construction, stdout/stderr parsing, session state, and
|
|
150
|
+
event emission.
|
|
151
|
+
|
|
152
|
+
This is useful when a caller needs to run the CLI somewhere other than the
|
|
153
|
+
current host process, for example through an existing container, remote shell,
|
|
154
|
+
or custom sandbox.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from agentshim import (
|
|
158
|
+
CodexCodingAgent,
|
|
159
|
+
CommandHandle,
|
|
160
|
+
CommandRequest,
|
|
161
|
+
CommandResult,
|
|
162
|
+
CommandStreamSink,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class MyCommandHandle:
|
|
167
|
+
def terminate(self) -> None:
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
def kill(self) -> None:
|
|
171
|
+
...
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class MyExecutor:
|
|
175
|
+
def find_binary(self, binary_name: str, env: dict[str, str]) -> str:
|
|
176
|
+
# This value becomes request.argv[0]. Container or remote executors can
|
|
177
|
+
# return the binary name if lookup happens in the target runtime.
|
|
178
|
+
return binary_name
|
|
179
|
+
|
|
180
|
+
def check_binary(self, binary_path: str, env: dict[str, str], *, timeout: int) -> None:
|
|
181
|
+
# Raise RuntimeError if the target CLI is unavailable. No-op is fine
|
|
182
|
+
# when validation is not cheap or is handled by the runtime.
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def run(self, request: CommandRequest, sink: CommandStreamSink) -> CommandResult:
|
|
186
|
+
handle = MyCommandHandle()
|
|
187
|
+
sink.started(handle)
|
|
188
|
+
|
|
189
|
+
stdout = ""
|
|
190
|
+
stderr = ""
|
|
191
|
+
|
|
192
|
+
# Run request.argv in your target runtime, with request.stdin,
|
|
193
|
+
# request.cwd, request.env, and request.timeout. Stream each complete
|
|
194
|
+
# line as it arrives, preserving trailing newlines when present.
|
|
195
|
+
line = "streamed output\n"
|
|
196
|
+
stdout += line
|
|
197
|
+
sink.stdout(line)
|
|
198
|
+
|
|
199
|
+
return CommandResult(returncode=0, stdout=stdout, stderr=stderr)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
agent = CodexCodingAgent(executor=MyExecutor())
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The default `HostCommandExecutor` preserves the normal local `subprocess`
|
|
206
|
+
behavior. `CodingAgent(provider=..., executor=...)` forwards the same executor
|
|
207
|
+
to the selected provider.
|
|
208
|
+
|
|
209
|
+
Executor contract:
|
|
210
|
+
|
|
211
|
+
- `CommandRequest.argv` is the complete provider CLI command. `argv[0]` is the
|
|
212
|
+
value returned by `find_binary`.
|
|
213
|
+
- `CommandRequest.stdin` is the prompt text to write to the command's standard
|
|
214
|
+
input, then stdin should be closed.
|
|
215
|
+
- `CommandRequest.cwd`, `env`, and `timeout` should be honored by the executor.
|
|
216
|
+
- Call `sink.started(handle)` once after the command starts. The handle only
|
|
217
|
+
needs `terminate()` and `kill()`.
|
|
218
|
+
- Call `sink.stdout(line)` and `sink.stderr(line)` as output is produced. Lines
|
|
219
|
+
should include trailing newlines when the underlying stream provided them.
|
|
220
|
+
- Return `CommandResult(returncode, stdout, stderr)` after the command exits.
|
|
221
|
+
The returned text should match what was streamed through the sink.
|
|
222
|
+
|
|
223
|
+
If you were using the pre-0.5 executor preview, replace
|
|
224
|
+
`run_streaming(cmd, ..., on_stdout, on_stderr, on_process_started)` with
|
|
225
|
+
`run(request, sink)`. The parser/event APIs remain internal; custom executors
|
|
226
|
+
only provide a stable command runtime.
|
|
227
|
+
|
|
228
|
+
### 4. Instantiate a Specific Provider Directly
|
|
145
229
|
|
|
146
230
|
If you already know which backend you want, construct the provider class
|
|
147
231
|
yourself.
|
|
@@ -163,7 +247,7 @@ The bundled provider classes are:
|
|
|
163
247
|
- `GeminiCodingAgent`
|
|
164
248
|
- `OpencodeCodingAgent`
|
|
165
249
|
|
|
166
|
-
###
|
|
250
|
+
### 5. Configure MCP Servers
|
|
167
251
|
|
|
168
252
|
Claude Code and Codex can be configured with MCP servers by passing
|
|
169
253
|
`HttpMcpServer` and `StdioMcpServer` objects at construction time.
|
|
@@ -240,7 +324,7 @@ Notes:
|
|
|
240
324
|
- Registration is import-driven. Your provider is available only after the module defining it has been imported in the current Python process.
|
|
241
325
|
- `list_providers()` returns canonical provider names only. Aliases resolve via `get_provider_class(...)` and `CodingAgent(provider=...)`.
|
|
242
326
|
- `register_provider(...)` rejects invalid names, abstract classes, and accidental name collisions unless you pass `overwrite=True`.
|
|
243
|
-
- If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `event_handler`, `event_handlers`, `mcp_servers`, and `
|
|
327
|
+
- If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `event_handler`, `event_handlers`, `mcp_servers`, `sandbox`, and `executor` as needed.
|
|
244
328
|
- If your provider needs extra constructor arguments beyond the shared portable set, pass them via `backend_kwargs={...}` when constructing `CodingAgent(...)`.
|
|
245
329
|
|
|
246
330
|
## Development
|
|
@@ -3,6 +3,15 @@ from .claude import ClaudeCodeCodingAgent
|
|
|
3
3
|
from .copilot import CopilotCodingAgent
|
|
4
4
|
from .codex import CodexCodingAgent
|
|
5
5
|
from .events import CompositeEventHandler, ConsoleEventHandler, NullEventHandler
|
|
6
|
+
from .executor import (
|
|
7
|
+
CallbackCommandStreamSink,
|
|
8
|
+
CommandExecutor,
|
|
9
|
+
CommandHandle,
|
|
10
|
+
CommandRequest,
|
|
11
|
+
CommandResult,
|
|
12
|
+
CommandStreamSink,
|
|
13
|
+
HostCommandExecutor,
|
|
14
|
+
)
|
|
6
15
|
from .gemini import GeminiCodingAgent
|
|
7
16
|
from .mcp_config import HttpMcpServer, McpServerConfig, StdioMcpServer
|
|
8
17
|
from .opencode import OpencodeCodingAgent
|
|
@@ -18,6 +27,13 @@ __all__ = [
|
|
|
18
27
|
"CompositeEventHandler",
|
|
19
28
|
"ConsoleEventHandler",
|
|
20
29
|
"NullEventHandler",
|
|
30
|
+
"CallbackCommandStreamSink",
|
|
31
|
+
"CommandExecutor",
|
|
32
|
+
"CommandHandle",
|
|
33
|
+
"CommandRequest",
|
|
34
|
+
"CommandResult",
|
|
35
|
+
"CommandStreamSink",
|
|
36
|
+
"HostCommandExecutor",
|
|
21
37
|
"CopilotCodingAgent",
|
|
22
38
|
"CodexCodingAgent",
|
|
23
39
|
"GeminiCodingAgent",
|
|
@@ -7,6 +7,7 @@ from collections.abc import Callable, Sequence
|
|
|
7
7
|
from typing import Any, TypeVar
|
|
8
8
|
|
|
9
9
|
from agentshim.events import AgentEventHandler
|
|
10
|
+
from agentshim.executor import CommandExecutor, CommandHandle
|
|
10
11
|
from agentshim.mcp_config import McpServerConfig
|
|
11
12
|
from agentshim.sandbox import SandboxConfig
|
|
12
13
|
|
|
@@ -83,7 +84,7 @@ class BaseAgentSession(ABC):
|
|
|
83
84
|
cwd: str | None = None,
|
|
84
85
|
timeout: int | None = None,
|
|
85
86
|
silent: bool | None = None,
|
|
86
|
-
on_process_started: Callable[[
|
|
87
|
+
on_process_started: Callable[[CommandHandle], None] | None = None,
|
|
87
88
|
) -> str:
|
|
88
89
|
"""Send ``prompt`` within an existing chat session."""
|
|
89
90
|
|
|
@@ -185,7 +186,7 @@ def register_provider(
|
|
|
185
186
|
*extra_names: str,
|
|
186
187
|
aliases: tuple[str, ...] = (),
|
|
187
188
|
overwrite: bool = False,
|
|
188
|
-
) -> Callable[[type[_T]], _T]:
|
|
189
|
+
) -> Callable[[type[_T]], type[_T]]:
|
|
189
190
|
"""Decorator to register a coding agent provider.
|
|
190
191
|
|
|
191
192
|
Registration is import-driven: the decorated class becomes available only
|
|
@@ -201,7 +202,7 @@ def register_provider(
|
|
|
201
202
|
all_aliases = (*extra_names, *aliases)
|
|
202
203
|
_PROVIDER_REGISTRY._normalize_names(canonical_name, all_aliases)
|
|
203
204
|
|
|
204
|
-
def decorator(cls: type[_T]) -> _T:
|
|
205
|
+
def decorator(cls: type[_T]) -> type[_T]:
|
|
205
206
|
return _PROVIDER_REGISTRY.register(
|
|
206
207
|
cls,
|
|
207
208
|
canonical_name=canonical_name,
|
|
@@ -237,6 +238,7 @@ class CodingAgent(BaseCodingAgent):
|
|
|
237
238
|
event_handlers: Sequence[AgentEventHandler] | None = None,
|
|
238
239
|
mcp_servers: Sequence[McpServerConfig] | None = None,
|
|
239
240
|
sandbox: bool | SandboxConfig | None = False,
|
|
241
|
+
executor: CommandExecutor | None = None,
|
|
240
242
|
backend_kwargs: dict[str, Any] | None = None,
|
|
241
243
|
) -> None:
|
|
242
244
|
requested_provider = _PROVIDER_REGISTRY.get_canonical_name(provider)
|
|
@@ -254,6 +256,8 @@ class CodingAgent(BaseCodingAgent):
|
|
|
254
256
|
portable_kwargs["mcp_servers"] = list(mcp_servers)
|
|
255
257
|
if sandbox is not None and sandbox is not False:
|
|
256
258
|
portable_kwargs["sandbox"] = sandbox
|
|
259
|
+
if executor is not None:
|
|
260
|
+
portable_kwargs["executor"] = executor
|
|
257
261
|
|
|
258
262
|
advanced_kwargs = dict(backend_kwargs or {})
|
|
259
263
|
overlapping_keys = sorted(portable_kwargs.keys() & advanced_kwargs.keys())
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import subprocess
|
|
3
2
|
import time
|
|
4
|
-
from collections.abc import Callable, Iterable
|
|
3
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
5
4
|
from typing import Any
|
|
6
5
|
|
|
7
6
|
from ..base import register_provider
|
|
8
7
|
from ..cli_agent import CLICodingAgent, CLIGenerationSession
|
|
9
8
|
from ..events import AgentEventHandler
|
|
9
|
+
from ..executor import CommandExecutor, CommandHandle
|
|
10
10
|
from ..mcp_config import HttpMcpServer, McpServerConfig
|
|
11
11
|
from ..sandbox import SandboxConfig, build_claude_sandbox_settings, resolve_sandbox
|
|
12
12
|
from ..usage import ProviderUsage, TokenUsage
|
|
@@ -130,8 +130,9 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
|
|
|
130
130
|
model: str | None = None,
|
|
131
131
|
event_handler: AgentEventHandler | None = None,
|
|
132
132
|
event_handlers: Iterable[AgentEventHandler] | None = None,
|
|
133
|
-
mcp_servers:
|
|
133
|
+
mcp_servers: Sequence[McpServerConfig] | None = None,
|
|
134
134
|
sandbox: bool | SandboxConfig = False,
|
|
135
|
+
executor: CommandExecutor | None = None,
|
|
135
136
|
):
|
|
136
137
|
"""Initialize the Claude Code coding agent.
|
|
137
138
|
|
|
@@ -145,8 +146,9 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
|
|
|
145
146
|
``--settings``. Only bash subprocess commands are
|
|
146
147
|
sandboxed; the Claude process itself is not wrapped.
|
|
147
148
|
Defaults to False (no sandbox).
|
|
149
|
+
executor: Optional command executor for binary lookup and process execution.
|
|
148
150
|
"""
|
|
149
|
-
super().__init__("claude", model, event_handler, event_handlers, mcp_servers)
|
|
151
|
+
super().__init__("claude", model, event_handler, event_handlers, mcp_servers, executor=executor)
|
|
150
152
|
self.sandbox = resolve_sandbox(sandbox)
|
|
151
153
|
if self.sandbox is not None:
|
|
152
154
|
# Without this, Claude Code cd's into a per-invocation scratch dir
|
|
@@ -223,7 +225,7 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
|
|
|
223
225
|
cwd: str | None = None,
|
|
224
226
|
timeout: int = 300,
|
|
225
227
|
silent: bool = False,
|
|
226
|
-
on_process_started: Callable[[
|
|
228
|
+
on_process_started: Callable[[CommandHandle], None] | None = None,
|
|
227
229
|
) -> ClaudeGenerationSession:
|
|
228
230
|
return ClaudeGenerationSession(
|
|
229
231
|
binary_name=self.binary_name,
|
|
@@ -235,5 +237,6 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
|
|
|
235
237
|
timeout=timeout,
|
|
236
238
|
silent=silent,
|
|
237
239
|
event_handler=self.event_handler,
|
|
240
|
+
executor=self.executor,
|
|
238
241
|
on_process_started=on_process_started,
|
|
239
242
|
)
|