agentshim 0.3.1__tar.gz → 0.4.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.
Files changed (66) hide show
  1. {agentshim-0.3.1 → agentshim-0.4.0}/PKG-INFO +77 -7
  2. {agentshim-0.3.1 → agentshim-0.4.0}/README.md +76 -6
  3. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/__init__.py +4 -0
  4. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/base.py +8 -27
  5. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/claude/agent.py +5 -37
  6. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/claude/events.py +1 -34
  7. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/cli_agent.py +20 -54
  8. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/codex/agent.py +7 -40
  9. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/codex/events.py +1 -33
  10. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/copilot/agent.py +18 -47
  11. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/copilot/events.py +1 -47
  12. agentshim-0.4.0/agentshim/events.py +263 -0
  13. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/gemini/agent.py +8 -84
  14. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/gemini/events.py +1 -26
  15. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/llm_client.py +4 -15
  16. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/opencode/agent.py +17 -36
  17. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/opencode/events.py +2 -35
  18. {agentshim-0.3.1 → agentshim-0.4.0}/pyproject.toml +1 -1
  19. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/cli_agent/test_agent_cli_cleanup.py +0 -1
  20. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/llm/test_claude_stream.py +25 -27
  21. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_claude.py +5 -9
  22. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_copilot.py +41 -11
  23. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_copilot_fixtures.py +7 -7
  24. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_event_parsing.py +0 -48
  25. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_resume.py +0 -6
  26. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_coding_agent_facade.py +12 -7
  27. agentshim-0.4.0/tests/unit/test_event_handlers.py +83 -0
  28. {agentshim-0.3.1 → agentshim-0.4.0}/uv.lock +1 -1
  29. agentshim-0.3.1/agentshim/events.py +0 -28
  30. agentshim-0.3.1/agentshim/trajectory.py +0 -168
  31. agentshim-0.3.1/tests/unit/test_coding_agent_recorder_default.py +0 -12
  32. {agentshim-0.3.1 → agentshim-0.4.0}/.github/workflows/ci.yml +0 -0
  33. {agentshim-0.3.1 → agentshim-0.4.0}/.github/workflows/publish.yml +0 -0
  34. {agentshim-0.3.1 → agentshim-0.4.0}/.gitignore +0 -0
  35. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/claude/__init__.py +0 -0
  36. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/claude/hooks/__init__.py +0 -0
  37. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/claude/hooks/confine_reads.py +0 -0
  38. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/claude_events.py +0 -0
  39. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/codex/__init__.py +0 -0
  40. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/codex_events.py +0 -0
  41. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/copilot/__init__.py +0 -0
  42. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/copilot_events.py +0 -0
  43. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/gemini/__init__.py +0 -0
  44. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/gemini_events.py +0 -0
  45. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/mcp_config.py +0 -0
  46. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/opencode/__init__.py +0 -0
  47. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/opencode_events.py +0 -0
  48. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/py.typed +0 -0
  49. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/sandbox.py +0 -0
  50. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/subagent.py +0 -0
  51. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/usage.py +0 -0
  52. {agentshim-0.3.1 → agentshim-0.4.0}/agentshim/utils.py +0 -0
  53. {agentshim-0.3.1 → agentshim-0.4.0}/tests/fixtures/copilot/session_turn_1.jsonl +0 -0
  54. {agentshim-0.3.1 → agentshim-0.4.0}/tests/fixtures/copilot/session_turn_2_resumed.jsonl +0 -0
  55. {agentshim-0.3.1 → agentshim-0.4.0}/tests/fixtures/copilot/streaming_dedup.jsonl +0 -0
  56. {agentshim-0.3.1 → agentshim-0.4.0}/tests/fixtures/copilot/tool_and_usage.jsonl +0 -0
  57. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/cli_agent/test_check_cli.py +0 -0
  58. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/cli_agent/test_cli_prompt_passing.py +0 -0
  59. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/llm/conftest.py +0 -0
  60. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/llm/test_gemini_fixture.py +0 -0
  61. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/llm/test_gemini_stream.py +0 -0
  62. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_codex.py +0 -0
  63. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_mcp_unsupported.py +0 -0
  64. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_agent_cli_sandbox.py +0 -0
  65. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_cli_agent_usage.py +0 -0
  66. {agentshim-0.3.1 → agentshim-0.4.0}/tests/unit/test_mcp_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentshim
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Provider-agnostic coding-agent CLI shims
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: litellm>=1.0.0
@@ -25,7 +25,6 @@ prompting, session resumption, event parsing, or MCP configuration.
25
25
  - MCP server config models for providers that support MCP
26
26
  - sandbox settings helpers for Claude Code
27
27
  - a lightweight LiteLLM client and subagent helper
28
- - trajectory/usage helpers used by higher-level runtimes
29
28
 
30
29
  ## Install
31
30
 
@@ -81,7 +80,79 @@ reply = agent.generate("Write a short summary of this codebase.", cwd=".")
81
80
  print(reply)
82
81
  ```
83
82
 
84
- ### 2. Instantiate a Specific Provider Directly
83
+ ### 2. Handle Agent Events
84
+
85
+ By default, `agentshim` prints provider events to the terminal through a
86
+ `ConsoleEventHandler`. That default is used only when you do not provide your
87
+ own event handler and `silent=False`.
88
+
89
+ If you pass `event_handler=...`, you take ownership of event handling. The
90
+ built-in console printer is not added implicitly, which avoids surprising
91
+ duplicate output.
92
+
93
+ ```python
94
+ from agentshim import CodingAgent
95
+
96
+
97
+ class MyHandler:
98
+ def on_thinking(self, text: str) -> None:
99
+ ...
100
+
101
+ def on_tool_call(self, tool: str, args=None) -> None:
102
+ ...
103
+
104
+ def on_tool_result(
105
+ self,
106
+ tool: str,
107
+ stdout: str = "",
108
+ stderr: str = "",
109
+ exit_code: int | None = None,
110
+ duration: float | None = None,
111
+ ) -> None:
112
+ ...
113
+
114
+ def on_usage(self, usage: dict) -> None:
115
+ ...
116
+
117
+
118
+ agent = CodingAgent(provider="claude", event_handler=MyHandler())
119
+ agent.generate("Inspect this repository.")
120
+ ```
121
+
122
+ To keep the default console output and add your own handler, compose them
123
+ explicitly:
124
+
125
+ ```python
126
+ from agentshim import CodingAgent, ConsoleEventHandler
127
+
128
+ agent = CodingAgent(
129
+ provider="claude",
130
+ event_handlers=[
131
+ ConsoleEventHandler(),
132
+ MyHandler(),
133
+ ],
134
+ )
135
+ agent.generate("Inspect this repository.")
136
+ ```
137
+
138
+ You can also build the composition yourself:
139
+
140
+ ```python
141
+ from agentshim import CompositeEventHandler, ConsoleEventHandler
142
+
143
+ handler = CompositeEventHandler([ConsoleEventHandler(), MyHandler()])
144
+ agent = CodingAgent(provider="codex", event_handler=handler)
145
+ ```
146
+
147
+ Use `silent=True` to suppress the default console handler when you have not
148
+ provided any handler:
149
+
150
+ ```python
151
+ agent = CodingAgent(provider="claude")
152
+ reply = agent.generate("Return only the answer.", silent=True)
153
+ ```
154
+
155
+ ### 3. Instantiate a Specific Provider Directly
85
156
 
86
157
  If you already know which backend you want, construct the provider class
87
158
  yourself.
@@ -103,7 +174,7 @@ The bundled provider classes are:
103
174
  - `GeminiCodingAgent`
104
175
  - `OpencodeCodingAgent`
105
176
 
106
- ### 3. Configure MCP Servers
177
+ ### 4. Configure MCP Servers
107
178
 
108
179
  Claude Code and Codex can be configured with MCP servers by passing
109
180
  `HttpMcpServer` and `StdioMcpServer` objects at construction time.
@@ -154,14 +225,13 @@ class MyAgent(BaseCodingAgent):
154
225
  self,
155
226
  model: str | None = None,
156
227
  region: str | None = None,
157
- recorder=None,
158
228
  event_handler=None,
229
+ event_handlers=None,
159
230
  mcp_servers=None,
160
231
  sandbox=False,
161
232
  ):
162
233
  self.model = model
163
234
  self.region = region
164
- self.recorder = recorder
165
235
  self.event_handler = event_handler
166
236
 
167
237
  def generate(self, prompt: str, cwd=None, timeout=300, silent=False) -> str:
@@ -181,7 +251,7 @@ Notes:
181
251
  - Registration is import-driven. Your provider is available only after the module defining it has been imported in the current Python process.
182
252
  - `list_providers()` returns canonical provider names only. Aliases resolve via `get_provider_class(...)` and `CodingAgent(provider=...)`.
183
253
  - `register_provider(...)` rejects invalid names, abstract classes, and accidental name collisions unless you pass `overwrite=True`.
184
- - If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `recorder`, `event_handler`, `mcp_servers`, and `sandbox` as needed.
254
+ - If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `event_handler`, `event_handlers`, `mcp_servers`, and `sandbox` as needed.
185
255
  - If your provider needs extra constructor arguments beyond the shared portable set, pass them via `backend_kwargs={...}` when constructing `CodingAgent(...)`.
186
256
 
187
257
  ## Development
@@ -14,7 +14,6 @@ prompting, session resumption, event parsing, or MCP configuration.
14
14
  - MCP server config models for providers that support MCP
15
15
  - sandbox settings helpers for Claude Code
16
16
  - a lightweight LiteLLM client and subagent helper
17
- - trajectory/usage helpers used by higher-level runtimes
18
17
 
19
18
  ## Install
20
19
 
@@ -70,7 +69,79 @@ reply = agent.generate("Write a short summary of this codebase.", cwd=".")
70
69
  print(reply)
71
70
  ```
72
71
 
73
- ### 2. Instantiate a Specific Provider Directly
72
+ ### 2. Handle Agent Events
73
+
74
+ By default, `agentshim` prints provider events to the terminal through a
75
+ `ConsoleEventHandler`. That default is used only when you do not provide your
76
+ own event handler and `silent=False`.
77
+
78
+ If you pass `event_handler=...`, you take ownership of event handling. The
79
+ built-in console printer is not added implicitly, which avoids surprising
80
+ duplicate output.
81
+
82
+ ```python
83
+ from agentshim import CodingAgent
84
+
85
+
86
+ class MyHandler:
87
+ def on_thinking(self, text: str) -> None:
88
+ ...
89
+
90
+ def on_tool_call(self, tool: str, args=None) -> None:
91
+ ...
92
+
93
+ def on_tool_result(
94
+ self,
95
+ tool: str,
96
+ stdout: str = "",
97
+ stderr: str = "",
98
+ exit_code: int | None = None,
99
+ duration: float | None = None,
100
+ ) -> None:
101
+ ...
102
+
103
+ def on_usage(self, usage: dict) -> None:
104
+ ...
105
+
106
+
107
+ agent = CodingAgent(provider="claude", event_handler=MyHandler())
108
+ agent.generate("Inspect this repository.")
109
+ ```
110
+
111
+ To keep the default console output and add your own handler, compose them
112
+ explicitly:
113
+
114
+ ```python
115
+ from agentshim import CodingAgent, ConsoleEventHandler
116
+
117
+ agent = CodingAgent(
118
+ provider="claude",
119
+ event_handlers=[
120
+ ConsoleEventHandler(),
121
+ MyHandler(),
122
+ ],
123
+ )
124
+ agent.generate("Inspect this repository.")
125
+ ```
126
+
127
+ You can also build the composition yourself:
128
+
129
+ ```python
130
+ from agentshim import CompositeEventHandler, ConsoleEventHandler
131
+
132
+ handler = CompositeEventHandler([ConsoleEventHandler(), MyHandler()])
133
+ agent = CodingAgent(provider="codex", event_handler=handler)
134
+ ```
135
+
136
+ Use `silent=True` to suppress the default console handler when you have not
137
+ provided any handler:
138
+
139
+ ```python
140
+ agent = CodingAgent(provider="claude")
141
+ reply = agent.generate("Return only the answer.", silent=True)
142
+ ```
143
+
144
+ ### 3. Instantiate a Specific Provider Directly
74
145
 
75
146
  If you already know which backend you want, construct the provider class
76
147
  yourself.
@@ -92,7 +163,7 @@ The bundled provider classes are:
92
163
  - `GeminiCodingAgent`
93
164
  - `OpencodeCodingAgent`
94
165
 
95
- ### 3. Configure MCP Servers
166
+ ### 4. Configure MCP Servers
96
167
 
97
168
  Claude Code and Codex can be configured with MCP servers by passing
98
169
  `HttpMcpServer` and `StdioMcpServer` objects at construction time.
@@ -143,14 +214,13 @@ class MyAgent(BaseCodingAgent):
143
214
  self,
144
215
  model: str | None = None,
145
216
  region: str | None = None,
146
- recorder=None,
147
217
  event_handler=None,
218
+ event_handlers=None,
148
219
  mcp_servers=None,
149
220
  sandbox=False,
150
221
  ):
151
222
  self.model = model
152
223
  self.region = region
153
- self.recorder = recorder
154
224
  self.event_handler = event_handler
155
225
 
156
226
  def generate(self, prompt: str, cwd=None, timeout=300, silent=False) -> str:
@@ -170,7 +240,7 @@ Notes:
170
240
  - Registration is import-driven. Your provider is available only after the module defining it has been imported in the current Python process.
171
241
  - `list_providers()` returns canonical provider names only. Aliases resolve via `get_provider_class(...)` and `CodingAgent(provider=...)`.
172
242
  - `register_provider(...)` rejects invalid names, abstract classes, and accidental name collisions unless you pass `overwrite=True`.
173
- - If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `recorder`, `event_handler`, `mcp_servers`, and `sandbox` as needed.
243
+ - If you want `CodingAgent(...)` to instantiate your provider, its constructor should accept the shared kwargs `model`, `event_handler`, `event_handlers`, `mcp_servers`, and `sandbox` as needed.
174
244
  - If your provider needs extra constructor arguments beyond the shared portable set, pass them via `backend_kwargs={...}` when constructing `CodingAgent(...)`.
175
245
 
176
246
  ## Development
@@ -2,6 +2,7 @@ from .base import BaseAgentSession, BaseCodingAgent, CodingAgent, get_provider_c
2
2
  from .claude import ClaudeCodeCodingAgent
3
3
  from .copilot import CopilotCodingAgent
4
4
  from .codex import CodexCodingAgent
5
+ from .events import CompositeEventHandler, ConsoleEventHandler, NullEventHandler
5
6
  from .gemini import GeminiCodingAgent
6
7
  from .mcp_config import HttpMcpServer, McpServerConfig, StdioMcpServer
7
8
  from .opencode import OpencodeCodingAgent
@@ -14,6 +15,9 @@ __all__ = [
14
15
  "get_provider_class",
15
16
  "list_providers",
16
17
  "register_provider",
18
+ "CompositeEventHandler",
19
+ "ConsoleEventHandler",
20
+ "NullEventHandler",
17
21
  "CopilotCodingAgent",
18
22
  "CodexCodingAgent",
19
23
  "GeminiCodingAgent",
@@ -9,7 +9,6 @@ from typing import Any, TypeVar
9
9
  from agentshim.events import AgentEventHandler
10
10
  from agentshim.mcp_config import McpServerConfig
11
11
  from agentshim.sandbox import SandboxConfig
12
- from agentshim.trajectory import NullTrajectoryRecorder, TrajectoryRecorderProtocol
13
12
 
14
13
  _T = TypeVar("_T")
15
14
  _READABLE_NAME_BOUNDARY = re.compile(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])")
@@ -28,7 +27,6 @@ def _readable_name_from_class_name(class_name: str) -> str:
28
27
  class BaseCodingAgent(ABC):
29
28
  """Abstract base class for coding agents."""
30
29
 
31
- recorder: TrajectoryRecorderProtocol = NullTrajectoryRecorder()
32
30
  event_handler: Any | None = None
33
31
 
34
32
  @property
@@ -113,9 +111,7 @@ class ProviderRegistry:
113
111
  if not normalized:
114
112
  raise ValueError("provider name must not be empty")
115
113
  if not _PROVIDER_NAME_PATTERN.fullmatch(normalized):
116
- raise ValueError(
117
- f"invalid provider name '{name}'; use lowercase letters, digits, hyphens, or underscores"
118
- )
114
+ raise ValueError(f"invalid provider name '{name}'; use lowercase letters, digits, hyphens, or underscores")
119
115
  return normalized
120
116
 
121
117
  def _normalize_names(self, canonical_name: str, aliases: tuple[str, ...]) -> tuple[str, tuple[str, ...]]:
@@ -147,9 +143,7 @@ class ProviderRegistry:
147
143
  all_names = (canonical, *normalized_aliases)
148
144
 
149
145
  collisions = [
150
- name
151
- for name in all_names
152
- if name in self._providers and self._providers[name] is not provider_cls
146
+ name for name in all_names if name in self._providers and self._providers[name] is not provider_cls
153
147
  ]
154
148
  if collisions and not overwrite:
155
149
  raise ValueError(
@@ -172,18 +166,14 @@ class ProviderRegistry:
172
166
  normalized = self._normalize_name(name)
173
167
  provider_cls = self._providers.get(normalized)
174
168
  if provider_cls is None:
175
- raise ValueError(
176
- f"Unknown coding agent provider '{name}'. Available providers: {self.list_providers()}"
177
- )
169
+ raise ValueError(f"Unknown coding agent provider '{name}'. Available providers: {self.list_providers()}")
178
170
  return provider_cls
179
171
 
180
172
  def get_canonical_name(self, name: str) -> str:
181
173
  normalized = self._normalize_name(name)
182
174
  canonical = self._canonical_names.get(normalized)
183
175
  if canonical is None:
184
- raise ValueError(
185
- f"Unknown coding agent provider '{name}'. Available providers: {self.list_providers()}"
186
- )
176
+ raise ValueError(f"Unknown coding agent provider '{name}'. Available providers: {self.list_providers()}")
187
177
  return canonical
188
178
 
189
179
 
@@ -243,8 +233,8 @@ class CodingAgent(BaseCodingAgent):
243
233
  self,
244
234
  provider: str,
245
235
  model: str | None = None,
246
- recorder: TrajectoryRecorderProtocol | None = None,
247
236
  event_handler: AgentEventHandler | None = None,
237
+ event_handlers: Sequence[AgentEventHandler] | None = None,
248
238
  mcp_servers: Sequence[McpServerConfig] | None = None,
249
239
  sandbox: bool | SandboxConfig | None = False,
250
240
  backend_kwargs: dict[str, Any] | None = None,
@@ -256,10 +246,10 @@ class CodingAgent(BaseCodingAgent):
256
246
  portable_kwargs: dict[str, Any] = {}
257
247
  if model is not None:
258
248
  portable_kwargs["model"] = model
259
- if recorder is not None:
260
- portable_kwargs["recorder"] = recorder
261
249
  if event_handler is not None:
262
250
  portable_kwargs["event_handler"] = event_handler
251
+ if event_handlers is not None:
252
+ portable_kwargs["event_handlers"] = list(event_handlers)
263
253
  if mcp_servers is not None:
264
254
  portable_kwargs["mcp_servers"] = list(mcp_servers)
265
255
  if sandbox is not None and sandbox is not False:
@@ -269,8 +259,7 @@ class CodingAgent(BaseCodingAgent):
269
259
  overlapping_keys = sorted(portable_kwargs.keys() & advanced_kwargs.keys())
270
260
  if overlapping_keys:
271
261
  raise ValueError(
272
- "backend_kwargs must not override portable CodingAgent arguments: "
273
- + ", ".join(overlapping_keys)
262
+ "backend_kwargs must not override portable CodingAgent arguments: " + ", ".join(overlapping_keys)
274
263
  )
275
264
 
276
265
  self._backend: BaseCodingAgent = provider_cls(**portable_kwargs, **advanced_kwargs)
@@ -296,14 +285,6 @@ class CodingAgent(BaseCodingAgent):
296
285
  def model(self, value: Any) -> None:
297
286
  self._backend.model = value # type: ignore[attr-defined]
298
287
 
299
- @property
300
- def recorder(self) -> TrajectoryRecorderProtocol:
301
- return self._backend.recorder
302
-
303
- @recorder.setter
304
- def recorder(self, value: TrajectoryRecorderProtocol) -> None:
305
- self._backend.recorder = value
306
-
307
288
  @property
308
289
  def event_handler(self) -> Any | None:
309
290
  return getattr(self._backend, "event_handler", None)
@@ -1,11 +1,9 @@
1
1
  import json
2
2
  import subprocess
3
3
  import time
4
- from collections.abc import Callable
4
+ from collections.abc import Callable, Iterable
5
5
  from typing import Any
6
6
 
7
- from agentshim.trajectory import TrajectoryRecorderProtocol
8
-
9
7
  from ..base import register_provider
10
8
  from ..cli_agent import CLICodingAgent, CLIGenerationSession
11
9
  from ..events import AgentEventHandler
@@ -44,11 +42,8 @@ class ClaudeGenerationSession(CLIGenerationSession):
44
42
  except json.JSONDecodeError:
45
43
  # Fallback for non-JSON lines - still accumulate them
46
44
  self.stdout_lines.append(line.rstrip())
47
- if not self.silent:
48
- if self._at_line_start:
49
- self._log_raw(f"{self.log_prefix} ")
50
- self._log_raw(line.rstrip() + "\n")
51
- self._at_line_start = True
45
+ if line.rstrip():
46
+ self.event_handler.on_thinking(line.rstrip() + "\n")
52
47
 
53
48
  def _handle_event(self, event: ClaudeEvent):
54
49
  """Handle a single parsed Claude event."""
@@ -63,9 +58,6 @@ class ClaudeGenerationSession(CLIGenerationSession):
63
58
 
64
59
  self._update_state(event)
65
60
 
66
- if not self.silent:
67
- self._render_event(event)
68
-
69
61
  def _update_state(self, event: ClaudeEvent):
70
62
  """Update internal state based on the event."""
71
63
  if isinstance(event, SystemEvent):
@@ -92,14 +84,7 @@ class ClaudeGenerationSession(CLIGenerationSession):
92
84
 
93
85
  start_time = self.tool_start_times.get(event.tool_id)
94
86
  duration = time.time() - start_time if start_time else None
95
- args = self.tool_args.get(event.tool_id, {})
96
87
 
97
- self.recorder.add_tool_call(
98
- tool=event.tool_name_resolved,
99
- args=args,
100
- stdout=event.output,
101
- duration=duration,
102
- )
103
88
  if self.event_handler:
104
89
  self.event_handler.on_tool_result(
105
90
  tool=event.tool_name_resolved,
@@ -128,20 +113,6 @@ class ClaudeGenerationSession(CLIGenerationSession):
128
113
  provider="claude",
129
114
  )
130
115
 
131
- def _render_event(self, event: ClaudeEvent):
132
- """Render the event to stdout."""
133
- if isinstance(event, TextEvent):
134
- self._print_stream_content(event.text)
135
- return
136
-
137
- if not self._at_line_start:
138
- self._log_raw("\n")
139
- self._at_line_start = True
140
-
141
- output = event.render(self.log_prefix)
142
- if output:
143
- self._log_raw(output + "\n")
144
-
145
116
  def run(self, prompt: str) -> str:
146
117
  """Execute the command and return the result."""
147
118
  super().run(prompt)
@@ -157,8 +128,8 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
157
128
  def __init__(
158
129
  self,
159
130
  model: str | None = None,
160
- recorder: TrajectoryRecorderProtocol | None = None,
161
131
  event_handler: AgentEventHandler | None = None,
132
+ event_handlers: Iterable[AgentEventHandler] | None = None,
162
133
  mcp_servers: list[McpServerConfig] | None = None,
163
134
  sandbox: bool | SandboxConfig = False,
164
135
  ):
@@ -166,7 +137,6 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
166
137
 
167
138
  Args:
168
139
  model: Optional model name to use with Claude Code. If None, uses default.
169
- recorder: Trajectory recorder instance.
170
140
  event_handler: Optional event handler for UI updates.
171
141
  mcp_servers: Optional list of MCP server configurations.
172
142
  sandbox: If True (or a ``SandboxConfig``), enable Claude Code's
@@ -176,7 +146,7 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
176
146
  sandboxed; the Claude process itself is not wrapped.
177
147
  Defaults to False (no sandbox).
178
148
  """
179
- super().__init__("claude", model, recorder, event_handler, mcp_servers)
149
+ super().__init__("claude", model, event_handler, event_handlers, mcp_servers)
180
150
  self.sandbox = resolve_sandbox(sandbox)
181
151
  if self.sandbox is not None:
182
152
  # Without this, Claude Code cd's into a per-invocation scratch dir
@@ -253,7 +223,6 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
253
223
  cwd: str | None = None,
254
224
  timeout: int = 300,
255
225
  silent: bool = False,
256
- recorder: TrajectoryRecorderProtocol | None = None,
257
226
  on_process_started: Callable[[subprocess.Popen[str]], None] | None = None,
258
227
  ) -> ClaudeGenerationSession:
259
228
  return ClaudeGenerationSession(
@@ -265,7 +234,6 @@ class ClaudeCodeCodingAgent(CLICodingAgent):
265
234
  cwd=cwd,
266
235
  timeout=timeout,
267
236
  silent=silent,
268
- recorder=recorder,
269
237
  event_handler=self.event_handler,
270
238
  on_process_started=on_process_started,
271
239
  )
@@ -1,18 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- from abc import ABC, abstractmethod
4
3
  from typing import Any, cast
5
4
 
6
- from ..utils import truncate_content, truncate_tool_params
7
5
 
8
-
9
- class ClaudeEvent(ABC):
6
+ class ClaudeEvent:
10
7
  """Base class for Claude Code stream events."""
11
8
 
12
- @abstractmethod
13
- def render(self, log_prefix: str) -> str | None:
14
- """Render the event as a string for terminal output."""
15
-
16
9
  @staticmethod
17
10
  def from_dict(data: dict[str, Any]) -> ClaudeEvent | None:
18
11
  """Factory method to create events from JSON data."""
@@ -71,10 +64,6 @@ class MultiEvent(ClaudeEvent):
71
64
  self.events = events
72
65
  self.usage = usage
73
66
 
74
- def render(self, log_prefix: str) -> str | None:
75
- # MultiEvent doesn't render itself; events are handled individually
76
- return None
77
-
78
67
 
79
68
  class SystemEvent(ClaudeEvent):
80
69
  """System initialization event.
@@ -87,10 +76,6 @@ class SystemEvent(ClaudeEvent):
87
76
  self.data = data
88
77
  self.session_id: str | None = data.get("session_id")
89
78
 
90
- def render(self, log_prefix: str) -> str | None:
91
- # System events are silent
92
- return None
93
-
94
79
 
95
80
  class TextEvent(ClaudeEvent):
96
81
  """Assistant text content event."""
@@ -98,10 +83,6 @@ class TextEvent(ClaudeEvent):
98
83
  def __init__(self, text: str):
99
84
  self.text = text
100
85
 
101
- def render(self, log_prefix: str) -> str | None:
102
- # Text rendering is handled specially due to streaming
103
- return self.text
104
-
105
86
 
106
87
  class ToolUseEvent(ClaudeEvent):
107
88
  """Tool call event from assistant."""
@@ -111,10 +92,6 @@ class ToolUseEvent(ClaudeEvent):
111
92
  self.tool_id = tool_id
112
93
  self.parameters = parameters
113
94
 
114
- def render(self, log_prefix: str) -> str:
115
- truncated = truncate_tool_params(self.tool_name, self.parameters)
116
- return f"{log_prefix} \033[34m[Tool Use] {self.tool_name} {truncated}\033[0m"
117
-
118
95
 
119
96
  class ToolResultEvent(ClaudeEvent):
120
97
  """Tool execution result event."""
@@ -129,12 +106,6 @@ class ToolResultEvent(ClaudeEvent):
129
106
  self.tool_id = tool_id
130
107
  self.tool_name_resolved: str = "Tool" # To be set externally
131
108
 
132
- def render(self, log_prefix: str) -> str:
133
- if not self.output:
134
- return f"{log_prefix} \033[32m{self.tool_name_resolved} ran successfully\033[0m"
135
- truncated = truncate_content(self.output)
136
- return f"{log_prefix} \033[32m[Tool Result] {truncated}\033[0m"
137
-
138
109
 
139
110
  class ResultEvent(ClaudeEvent):
140
111
  """Final session summary event."""
@@ -152,7 +123,3 @@ class ResultEvent(ClaudeEvent):
152
123
  self.usage = usage
153
124
  self.total_cost_usd = total_cost_usd
154
125
  self.duration_ms = duration_ms
155
-
156
- def render(self, log_prefix: str) -> str | None:
157
- # Result events are silent (result is captured separately)
158
- return None