Current section

46 Versions

Jump to

Compare versions

82 files changed
+4748 additions
-2175 deletions
  @@ -7,6 +7,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7 7
8 8 ## [Unreleased]
9 9
10 + ## [0.30.0] - 2026-03-11 | CC 2.1.72
11 +
12 + **Breaking release** — This release reorganizes the public API and module structure for long-term clarity. See the Changed section for a migration summary.
13 +
14 + ### Added
15 +
16 + #### New features
17 +
18 + - **9 new content block types**`ClaudeCode.Content.ServerToolUseBlock`, `ClaudeCode.Content.ServerToolResultBlock`, `ClaudeCode.Content.MCPToolUseBlock`, `ClaudeCode.Content.MCPToolResultBlock`, `ClaudeCode.Content.ImageBlock`, `ClaudeCode.Content.DocumentBlock`, `ClaudeCode.Content.RedactedThinkingBlock`, `ClaudeCode.Content.CompactionBlock`, and `ClaudeCode.Content.ContainerUploadBlock` for parsing all CLI content types. ([80e2d0c])
19 + - **Forward-compatible message parsing** — Unknown content block types and message types are now preserved as maps instead of being silently dropped, preventing data loss when the CLI adds new types. ([80e2d0c])
20 + - **Extensible parser registries** — Application config options `:content_parsers`, `:message_parsers`, and `:system_parsers` allow registering custom parser functions for new CLI types without forking the SDK. ([80e2d0c])
21 + - **New control API functions**`ClaudeCode.Session.set_mcp_servers/2`, `ClaudeCode.Session.mcp_reconnect/2`, `ClaudeCode.Session.mcp_toggle/3`, and `ClaudeCode.Session.stop_task/2` for runtime session control. ([10c4c3e], [3ff858e])
22 + - **Initialize response accessors**`ClaudeCode.Session.supported_commands/1`, `ClaudeCode.Session.supported_models/1`, `ClaudeCode.Session.supported_agents/1`, and `ClaudeCode.Session.account_info/1` return typed structs from the initialization handshake. ([3ff858e])
23 + - **Typed response structs** — New `ClaudeCode.Session.SlashCommand`, `ClaudeCode.Model.Info`, `ClaudeCode.Session.AgentInfo`, `ClaudeCode.Session.AccountInfo`, and `ClaudeCode.MCP.Status` structs for structured access to CLI responses. ([e3725a4], [90cb40e], [baaf9a7])
24 + - **Inbound control request handling**`ClaudeCode.Adapter.Port` now handles CLI-initiated `elicitation` requests (logged, returns error) and `cancel` requests (cancels pending control requests). ([d565e20])
25 + - **`:dry_run` option for `ClaudeCode.Session.rewind_files/2`** — Preview which files would be rewound without actually reverting them. ([9d9e8ac])
26 + - **`ClaudeCode.cli_version/0`** — Returns the configured CLI version the SDK is using. ([dd2a771])
27 + - **`ClaudeCode.Model.Effort` module** — Shared type (`t()`) and `parse/1` function for effort levels (`:low`, `:medium`, `:high`, `:max`). `ClaudeCode.Model.Info.supported_effort_levels` now returns atoms instead of strings. ([718b0b5])
28 +
29 + #### New struct fields (CLI 2.1.72 sync)
30 +
31 + - `ClaudeCode.Message.SystemMessage.FilesPersisted` — Added `failed` and `processed_at` fields. ([f215376])
32 + - `ClaudeCode.Message.RateLimitEvent` — Added `overage_status`, `overage_resets_at`, `overage_disabled_reason`, `is_using_overage`, `surpassed_threshold`, and `rate_limit_type` fields. ([412c802])
33 + - `ClaudeCode.Sandbox` — Added `enable_weaker_network_isolation` field. ([a84106d])
34 + - `ClaudeCode.Message.SystemMessage.TaskProgress` — Added optional `:summary` field (AI-generated progress summary, emitted when `agentProgressSummaries` is enabled). ([89126bb])
35 + - `ClaudeCode.Message.SystemMessage.TaskStarted` — Added optional `:prompt` field (the prompt that started the task). ([89126bb])
36 + - `ClaudeCode.Model.Info` — Added optional `:supports_auto_mode` field (boolean, defaults to `false`). ([89126bb])
37 +
38 + ### Fixed
39 +
40 + - **Race condition in `ClaudeCode.Session` queued request error handling** — When the CLI adapter failed before the stream caller registered as a subscriber, the error was silently lost and the stream would halt normally instead of raising. ([d52f77d])
41 + - **Atom-safe map key conversion from CLI JSON payloads** — Replaced unbounded `String.to_atom` with `safe_atomize_keys/1` (uses `binary_to_existing_atom`) for map keys parsed from external CLI input. Unknown keys stay as strings instead of creating new atoms, preventing atom table exhaustion. ([3c9dc90])
42 + - **`ClaudeCode.Message.AssistantMessage` now parses `:refusal` stop reason** — Aligns with `ClaudeCode.Message.ResultMessage` and the stop_reason typespec. ([28c15a3])
43 + - **Streaming docs/examples corrected** — Documentation and examples now consistently show partial streaming configured at session start and streamed with `ClaudeCode.stream/2` or `ClaudeCode.stream/3`.
44 +
45 + ### Changed
46 +
47 + #### API restructure and module reorganization
48 +
49 + The top-level `ClaudeCode` module is now slimmed to 4 core functions: `start_link/1`, `stream/3`, `query/2`, `stop/1`. All session management, runtime configuration, MCP management, and introspection functions moved to `ClaudeCode.Session`. The GenServer implementation moved to Session.Server (internal module). ([b5a37b1])
50 +
51 + **Module moves** (old → new):
52 +
53 + | Old location | New location |
54 + |---|---|
55 + | ClaudeCode.get\_session\_id/1 | `ClaudeCode.Session.session_id/1` |
56 + | ClaudeCode.get\_mcp\_status/1 | `ClaudeCode.Session.mcp_status/1` |
57 + | ClaudeCode.get\_server\_info/1 | `ClaudeCode.Session.server_info/1` |
58 + | ClaudeCode.Types | Extracted to `ClaudeCode.Session.PermissionMode`, `ClaudeCode.Session.PermissionDenial`, `ClaudeCode.Model.Usage`, `ClaudeCode.Usage` |
59 + | ClaudeCode.StopReason | Inlined into `ClaudeCode.Message` as `stop_reason()` type and `parse_stop_reason/1` |
60 + | ClaudeCode.PermissionMode | `ClaudeCode.Session.PermissionMode` |
61 + | ClaudeCode.PermissionDenial | `ClaudeCode.Session.PermissionDenial` |
62 + | ClaudeCode.AccountInfo | `ClaudeCode.Session.AccountInfo` |
63 + | ClaudeCode.AgentInfo | `ClaudeCode.Session.AgentInfo` |
64 + | ClaudeCode.SlashCommand | `ClaudeCode.Session.SlashCommand` |
65 + | ClaudeCode.McpServerStatus | `ClaudeCode.MCP.Status` |
66 + | ClaudeCode.McpSetServersResult | Replaced with typed map |
67 + | ClaudeCode.RewindFilesResult | Replaced with typed map |
68 + | System message subtypes (e.g., CompactBoundaryMessage) | `ClaudeCode.Message.SystemMessage.*` namespace (e.g., `ClaudeCode.Message.SystemMessage.CompactBoundary`) |
69 +
70 + New system message subtype: `ClaudeCode.Message.SystemMessage.Init` extracted from `SystemMessage`. ([80e2d0c])
71 +
72 + #### Other changes
73 +
74 + - **`ClaudeCode.Session.server_info/1` returns atom-keyed map** — Uses atom keys (`%{commands: [...], models: [...], agents: [...], account: %AccountInfo{}, ...}`) instead of string keys. ([8bc23e3])
75 + - **`ClaudeCode.Session.mcp_status/1` returns `[ClaudeCode.MCP.Status.t()]`** — Returns the server list directly instead of `%{"servers" => [...]}`. ([ffa1b81])
76 + - **`ClaudeCode.Session.rewind_files/2` returns a typed map** — Returns a typed map instead of a raw map. ([7acdfd5], [baaf9a7])
77 + - **`ClaudeCode.Model.Info` boolean fields default to `false`** — Fields like `supports_thinking`, `supports_computer_use`, etc. now default to `false` instead of `nil`. ([f6b38e5])
78 + - **Upgraded bundled CLI to 2.1.72** ([89126bb])
79 +
80 + ### Removed
81 +
82 + - **`ClaudeCode.Types` module** — See module moves table above. ([80e2d0c])
83 + - **`ClaudeCode.McpSetServersResult` and `ClaudeCode.RewindFilesResult` structs** — Replaced with typed maps returned directly from the control protocol. ([baaf9a7])
84 + - **`set_max_thinking_tokens` control-plane function** — Removed the deprecated mid-session `set_max_thinking_tokens` control command (matches TS SDK deprecation). Use the `:thinking` session option instead. ([b5a37b1])
85 +
10 86 ## [0.29.0] - 2026-03-02 | CC 2.1.62
11 87
12 88 ### Added
  @@ -172,7 +248,7 @@ Hooks, permissions, and MCP tools that run inside your application process — n
172 248
173 249 #### Session control and new options
174 250
175 - - **`ClaudeCode.interrupt/1`** - Fire-and-forget signal to cancel a running generation mid-stream ([5c04495])
251 + - **ClaudeCode.interrupt/1** - Fire-and-forget signal to cancel a running generation mid-stream ([5c04495])
176 252 - **`:extra_args`** - Pass-through arbitrary CLI flags not covered by named options ([5c04495])
177 253 - **`:max_buffer_size`** - Protection against unbounded buffer growth from large JSON responses. Default: 1MB ([5c04495])
178 254
  @@ -193,11 +269,11 @@ config :claude_code, cli_path: :global
193 269
194 270 Runtime control of sessions without restarting. See [Sessions — Runtime Control](docs/guides/sessions.md#runtime-control).
195 271
196 - - `ClaudeCode.set_model/2` - Change the model mid-conversation ([7ba2007])
197 - - `ClaudeCode.set_permission_mode/2` - Change the permission mode mid-conversation ([7ba2007])
198 - - `ClaudeCode.get_mcp_status/1` - Query MCP server connection status ([7ba2007])
199 - - `ClaudeCode.get_server_info/1` - Get server info cached from handshake ([228c57f])
200 - - `ClaudeCode.rewind_files/2` - Rewind files to a checkpoint. See [File Checkpointing](docs/guides/file-checkpointing.md). ([7ba2007])
272 + - ClaudeCode.set_model/2 - Change the model mid-conversation ([7ba2007])
273 + - ClaudeCode.set_permission_mode/2 - Change the permission mode mid-conversation ([7ba2007])
274 + - ClaudeCode.get_mcp_status/1 - Query MCP server connection status ([7ba2007])
275 + - ClaudeCode.get_server_info/1 - Get server info cached from handshake ([228c57f])
276 + - ClaudeCode.rewind_files/2 - Rewind files to a checkpoint. See [File Checkpointing](docs/guides/file-checkpointing.md). ([7ba2007])
201 277 - Returns `{:error, :not_supported}` for adapters without control protocol support
202 278 - **Initialize handshake** - Adapter sends `initialize` request on startup, transitions through `:initializing``:ready`. Agents are now delivered through the handshake (matching the Python SDK) instead of as a CLI flag. See [Subagents](docs/guides/subagents.md). ([228c57f], [2a4473b])
203 279
  @@ -216,7 +292,7 @@ Swappable backends for different execution environments.
216 292
217 293 - **`ClaudeCode.Adapter` behaviour** - 4 callbacks: `start_link/2`, `send_query/4`, `health/1`, `stop/1` ([1582644])
218 294 - **Adapter notification helpers** - `notify_message/2`, `notify_done/2`, `notify_error/2`, `notify_status/2` ([1704326])
219 - - **`ClaudeCode.health/1`** - Check adapter health (`:healthy` | `:degraded` | `{:unhealthy, reason}`). See [Hosting](docs/guides/hosting.md). ([383dda6])
295 + - **ClaudeCode.health/1** - Check adapter health (`:healthy` | `:degraded` | `{:unhealthy, reason}`). See [Hosting](docs/guides/hosting.md). ([383dda6])
220 296
221 297 #### CLI management
222 298
  @@ -279,7 +355,7 @@ Swappable backends for different execution environments.
279 355 ### Added
280 356
281 357 - **Session history reading** - Read and parse conversation history from session files ([ad737ea])
282 - - `ClaudeCode.conversation/2` - Read conversation (user/assistant messages) by session ID
358 + - ClaudeCode.conversation/2 - Read conversation (user/assistant messages) by session ID
283 359 - `ClaudeCode.History.list_projects/1` - List all projects with session history
284 360 - `ClaudeCode.History.list_sessions/2` - List all sessions for a project
285 361 - `ClaudeCode.History.read_session/2` - Read all raw entries from a session (low-level)
  @@ -534,7 +610,7 @@ Swappable backends for different execution environments.
534 610 - Auto-connect on first query, auto-disconnect on session stop
535 611 - Multi-turn conversations without subprocess restarts
536 612 - New `:resume` option in `start_link/1` for resuming sessions
537 - - New `ClaudeCode.get_session_id/1` and `ClaudeCode.Input` module
613 + - New ClaudeCode.get_session_id/1 and `ClaudeCode.Input` module
538 614 - **Extended thinking support** - `ClaudeCode.Content.Thinking` for reasoning blocks
539 615 - Stream utilities: `ClaudeCode.Stream.thinking_content/1`, `ClaudeCode.Stream.thinking_deltas/1`
540 616 - `StreamEvent` helpers: `thinking_delta?/1`, `get_thinking/1`
  @@ -43,7 +43,7 @@ Add to `mix.exs`:
43 43
44 44 ```elixir
45 45 def deps do
46 - [{:claude_code, "~> 0.29"}]
46 + [{:claude_code, "~> 0.30"}]
47 47 end
48 48 ```
49 49
  @@ -75,6 +75,10 @@ session
75 75 # => "Your favorite language is Elixir!"
76 76 ```
77 77
78 + ## Example Scripts
79 +
80 + See [`examples/README.md`](examples/README.md) for runnable examples including streaming and character-level streaming with `include_partial_messages: true`.
81 +
78 82 ## Features
79 83
80 84 ### In-Process Custom Tools
  @@ -120,9 +124,11 @@ Pass per-session context via assigns for scoped tools in LiveView:
120 124 Native Elixir Streams with character-level deltas, composable pipelines, and direct LiveView integration:
121 125
122 126 ```elixir
127 + {:ok, session} = ClaudeCode.start_link(include_partial_messages: true)
128 +
123 129 # Character-level streaming
124 130 session
125 - |> ClaudeCode.stream("Explain recursion", include_partial_messages: true)
131 + |> ClaudeCode.stream("Explain recursion")
126 132 |> ClaudeCode.Stream.text_deltas()
127 133 |> Enum.each(&IO.write/1)
128 134
  @@ -130,19 +136,19 @@ session
130 136 pid = self()
131 137 Task.start(fn ->
132 138 session
133 - |> ClaudeCode.stream(message, include_partial_messages: true)
139 + |> ClaudeCode.stream(message)
134 140 |> ClaudeCode.Stream.text_deltas()
135 141 |> Enum.each(&send(pid, {:chunk, &1}))
136 142 end)
137 143
138 144 # PubSub broadcasting
139 145 session
140 - |> ClaudeCode.stream("Generate report", include_partial_messages: true)
146 + |> ClaudeCode.stream("Generate report")
141 147 |> ClaudeCode.Stream.text_deltas()
142 148 |> Enum.each(&Phoenix.PubSub.broadcast(MyApp.PubSub, "chat:#{id}", {:chunk, &1}))
143 149 ```
144 150
145 - Stream helpers: `text_deltas/1`, `thinking_deltas/1`, `text_content/1`, `tool_uses/1`, `final_text/1`, `collect/1`, `buffered_text/1`, and more.
151 + Stream helpers: `text_deltas/1`, `thinking_deltas/1`, `content_deltas/1`, `text_content/1`, `tool_uses/1`, `final_text/1`, `final_result/1`, `collect/1`, and more.
146 152
147 153 [Streaming guide →](docs/guides/streaming-output.md)
148 154
  @@ -249,7 +255,7 @@ Resume conversations, fork sessions, and read history:
249 255 session |> ClaudeCode.stream("Remember: the code is 12345") |> Stream.run()
250 256
251 257 # Save session ID, stop, resume later
252 - session_id = ClaudeCode.get_session_id(session)
258 + session_id = ClaudeCode.Session.session_id(session)
253 259 ClaudeCode.stop(session)
254 260
255 261 {:ok, resumed} = ClaudeCode.start_link(resume: session_id)
  @@ -258,8 +264,8 @@ ClaudeCode.stop(session)
258 264 {:ok, forked} = ClaudeCode.start_link(resume: session_id, fork_session: true)
259 265
260 266 # Runtime controls without restarting
261 - ClaudeCode.set_model(session, "claude-sonnet-4-5-20250929")
262 - ClaudeCode.set_permission_mode(session, :accept_edits)
267 + ClaudeCode.Session.set_model(session, "claude-sonnet-4-5-20250929")
268 + ClaudeCode.Session.set_permission_mode(session, :accept_edits)
263 269 ```
264 270
265 271 [Sessions guide →](docs/guides/sessions.md)
  @@ -310,7 +316,7 @@ checkpoint_id =
310 316 end)
311 317
312 318 # Undo all file changes back to that checkpoint
313 - ClaudeCode.rewind_files(session, checkpoint_id)
319 + ClaudeCode.Session.rewind_files(session, checkpoint_id)
314 320 ```
315 321
316 322 [File checkpointing guide →](docs/guides/file-checkpointing.md)
  @@ -3,7 +3,7 @@
3 3 <<"https://github.com/guess/claude_code/blob/main/CHANGELOG.md">>},
4 4 {<<"GitHub">>,<<"https://github.com/guess/claude_code">>}]}.
5 5 {<<"name">>,<<"claude_code">>}.
6 - {<<"version">>,<<"0.29.0">>}.
6 + {<<"version">>,<<"0.30.0">>}.
7 7 {<<"description">>,
8 8 <<67,108,97,117,100,101,32,65,103,101,110,116,32,83,68,75,32,102,111,114,32,
9 9 69,108,105,120,105,114,32,226,128,147,32,66,117,105,108,100,32,65,73,32,97,
  @@ -13,31 +13,53 @@
13 13 {<<"app">>,<<"claude_code">>}.
14 14 {<<"files">>,
15 15 [<<"lib">>,<<"lib/claude_code">>,<<"lib/claude_code/adapter.ex">>,
16 - <<"lib/claude_code/supervisor.ex">>,<<"lib/claude_code/system_cmd.ex">>,
17 - <<"lib/claude_code/message.ex">>,<<"lib/claude_code/session.ex">>,
18 - <<"lib/claude_code/hook.ex">>,<<"lib/claude_code/test">>,
19 - <<"lib/claude_code/test/factory.ex">>,<<"lib/claude_code/stream.ex">>,
20 - <<"lib/claude_code/types.ex">>,<<"lib/claude_code/test.ex">>,
21 - <<"lib/claude_code/options.ex">>,<<"lib/claude_code/content">>,
16 + <<"lib/claude_code/supervisor.ex">>,<<"lib/claude_code/message.ex">>,
17 + <<"lib/claude_code/session.ex">>,<<"lib/claude_code/hook.ex">>,
18 + <<"lib/claude_code/test">>,<<"lib/claude_code/test/factory.ex">>,
19 + <<"lib/claude_code/stream.ex">>,<<"lib/claude_code/test.ex">>,
20 + <<"lib/claude_code/options.ex">>,<<"lib/claude_code/system.ex">>,
21 + <<"lib/claude_code/content">>,
22 + <<"lib/claude_code/content/document_block.ex">>,
23 + <<"lib/claude_code/content/image_block.ex">>,
24 + <<"lib/claude_code/content/container_upload_block.ex">>,
25 + <<"lib/claude_code/content/server_tool_result_block.ex">>,
22 26 <<"lib/claude_code/content/text_block.ex">>,
27 + <<"lib/claude_code/content/redacted_thinking_block.ex">>,
28 + <<"lib/claude_code/content/server_tool_use_block.ex">>,
23 29 <<"lib/claude_code/content/tool_use_block.ex">>,
24 30 <<"lib/claude_code/content/tool_result_block.ex">>,
31 + <<"lib/claude_code/content/compaction_block.ex">>,
32 + <<"lib/claude_code/content/mcp_tool_use_block.ex">>,
33 + <<"lib/claude_code/content/mcp_tool_result_block.ex">>,
25 34 <<"lib/claude_code/content/thinking_block.ex">>,
26 35 <<"lib/claude_code/message">>,
27 36 <<"lib/claude_code/message/system_message.ex">>,
28 37 <<"lib/claude_code/message/tool_use_summary_message.ex">>,
29 - <<"lib/claude_code/message/compact_boundary_message.ex">>,
30 38 <<"lib/claude_code/message/auth_status_message.ex">>,
31 39 <<"lib/claude_code/message/prompt_suggestion_message.ex">>,
32 40 <<"lib/claude_code/message/rate_limit_event.ex">>,
33 41 <<"lib/claude_code/message/tool_progress_message.ex">>,
34 42 <<"lib/claude_code/message/partial_assistant_message.ex">>,
35 43 <<"lib/claude_code/message/user_message.ex">>,
44 + <<"lib/claude_code/message/system_message">>,
45 + <<"lib/claude_code/message/system_message/task_notification.ex">>,
46 + <<"lib/claude_code/message/system_message/compact_boundary.ex">>,
47 + <<"lib/claude_code/message/system_message/hook_started.ex">>,
48 + <<"lib/claude_code/message/system_message/elicitation_complete.ex">>,
49 + <<"lib/claude_code/message/system_message/local_command_output.ex">>,
50 + <<"lib/claude_code/message/system_message/hook_response.ex">>,
51 + <<"lib/claude_code/message/system_message/init.ex">>,
52 + <<"lib/claude_code/message/system_message/hook_progress.ex">>,
53 + <<"lib/claude_code/message/system_message/task_started.ex">>,
54 + <<"lib/claude_code/message/system_message/status.ex">>,
55 + <<"lib/claude_code/message/system_message/task_progress.ex">>,
56 + <<"lib/claude_code/message/system_message/files_persisted.ex">>,
36 57 <<"lib/claude_code/message/result_message.ex">>,
37 58 <<"lib/claude_code/message/assistant_message.ex">>,
38 59 <<"lib/claude_code/mcp">>,<<"lib/claude_code/mcp/server.ex">>,
39 - <<"lib/claude_code/mcp/router.ex">>,<<"lib/claude_code/adapter">>,
40 - <<"lib/claude_code/adapter/test.ex">>,<<"lib/claude_code/adapter/port.ex">>,
60 + <<"lib/claude_code/mcp/router.ex">>,<<"lib/claude_code/mcp/status.ex">>,
61 + <<"lib/claude_code/adapter">>,<<"lib/claude_code/adapter/test.ex">>,
62 + <<"lib/claude_code/adapter/port.ex">>,
41 63 <<"lib/claude_code/adapter/control_handler.ex">>,
42 64 <<"lib/claude_code/adapter/node.ex">>,<<"lib/claude_code/adapter/node">>,
43 65 <<"lib/claude_code/adapter/node/callback_proxy.ex">>,
  @@ -46,15 +68,26 @@
46 68 <<"lib/claude_code/adapter/port/resolver.ex">>,<<"lib/claude_code/cli">>,
47 69 <<"lib/claude_code/cli/control.ex">>,<<"lib/claude_code/cli/command.ex">>,
48 70 <<"lib/claude_code/cli/parser.ex">>,<<"lib/claude_code/cli/input.ex">>,
49 - <<"lib/claude_code/sandbox">>,<<"lib/claude_code/sandbox/filesystem.ex">>,
71 + <<"lib/claude_code/cli/control">>,
72 + <<"lib/claude_code/cli/control/types.ex">>,<<"lib/claude_code/system">>,
73 + <<"lib/claude_code/system/default.ex">>,<<"lib/claude_code/sandbox">>,
74 + <<"lib/claude_code/sandbox/filesystem.ex">>,
50 75 <<"lib/claude_code/sandbox/helpers.ex">>,
51 - <<"lib/claude_code/sandbox/network.ex">>,<<"lib/claude_code/history.ex">>,
52 - <<"lib/claude_code/sandbox.ex">>,<<"lib/claude_code/json_encoder.ex">>,
76 + <<"lib/claude_code/sandbox/network.ex">>,<<"lib/claude_code/usage.ex">>,
77 + <<"lib/claude_code/model">>,<<"lib/claude_code/model/info.ex">>,
78 + <<"lib/claude_code/model/usage.ex">>,<<"lib/claude_code/model/effort.ex">>,
79 + <<"lib/claude_code/history.ex">>,<<"lib/claude_code/sandbox.ex">>,
80 + <<"lib/claude_code/model.ex">>,<<"lib/claude_code/json_encoder.ex">>,
53 81 <<"lib/claude_code/mcp.ex">>,<<"lib/claude_code/content.ex">>,
54 82 <<"lib/claude_code/agent.ex">>,<<"lib/claude_code/hook">>,
55 83 <<"lib/claude_code/hook/registry.ex">>,
56 - <<"lib/claude_code/hook/response.ex">>,<<"lib/claude_code/system_cmd">>,
57 - <<"lib/claude_code/system_cmd/default.ex">>,<<"lib/mix">>,
84 + <<"lib/claude_code/hook/response.ex">>,<<"lib/claude_code/map_utils.ex">>,
85 + <<"lib/claude_code/session">>,<<"lib/claude_code/session/account_info.ex">>,
86 + <<"lib/claude_code/session/slash_command.ex">>,
87 + <<"lib/claude_code/session/server.ex">>,
88 + <<"lib/claude_code/session/permission_mode.ex">>,
89 + <<"lib/claude_code/session/agent_info.ex">>,
90 + <<"lib/claude_code/session/permission_denial.ex">>,<<"lib/mix">>,
58 91 <<"lib/mix/tasks">>,<<"lib/mix/tasks/claude_code.path.ex">>,
59 92 <<"lib/mix/tasks/claude_code.uninstall.ex">>,
60 93 <<"lib/mix/tasks/claude_code.install.ex">>,<<"lib/claude_code.ex">>,
  @@ -2,23 +2,22 @@ defmodule ClaudeCode do
2 2 @moduledoc """
3 3 Elixir SDK for Claude Code CLI.
4 4
5 - This module provides the main interface for interacting with Claude Code
6 - through the command-line interface. It manages sessions as GenServer processes
7 - that maintain persistent CLI subprocesses for efficient bidirectional communication.
5 + This module provides the main entry points for interacting with Claude Code.
6 + For session management, runtime configuration, and introspection, see
7 + `ClaudeCode.Session`.
8 8
9 9 ## API Overview
10 10
11 11 | Function | Purpose |
12 12 |----------|---------|
13 - | `start_link/1` | Start a session (with optional `resume: id`) |
14 - | `stop/1` | Stop a session |
15 - | `stream/3` | Send prompt to session, get message stream |
13 + | `start_link/1` | Start a session |
14 + | `stream/3` | Send prompt, get message stream |
16 15 | `query/2` | One-off query (auto start/stop) |
17 - | `health/1` | Check adapter health status |
16 + | `stop/1` | Stop a session |
18 17
19 18 ## Quick Start
20 19
21 - # Multi-turn conversation (primary API)
20 + # Multi-turn conversation
22 21 {:ok, session} = ClaudeCode.start_link(api_key: "sk-ant-...")
23 22
24 23 ClaudeCode.stream(session, "What is 5 + 3?")
  @@ -33,20 +32,8 @@ defmodule ClaudeCode do
33 32 {:ok, result} = ClaudeCode.query("What is 2 + 2?", api_key: "sk-ant-...")
34 33 IO.puts(result)
35 34
36 - ## Session Lifecycle
37 -
38 - Sessions automatically connect to the Claude CLI on startup and disconnect on stop.
39 - The persistent connection enables:
40 - - Efficient multi-turn conversations without CLI restart overhead
41 - - Automatic session continuity via session IDs
42 - - Real-time streaming of responses
43 -
44 35 ## Supervision for Production
45 36
46 - For production applications, use the supervisor for fault tolerance and
47 - automatic restart capabilities:
48 -
49 - # In your application supervision tree
50 37 children = [
51 38 {ClaudeCode.Supervisor, [
52 39 [name: :code_reviewer, api_key: api_key, system_prompt: "You review Elixir code"],
  @@ -56,7 +43,6 @@ defmodule ClaudeCode do
56 43
57 44 Supervisor.start_link(children, strategy: :one_for_one)
58 45
59 - # Access supervised sessions from anywhere
60 46 :code_reviewer
61 47 |> ClaudeCode.stream("Review this function")
62 48 |> ClaudeCode.Stream.text_content()
  @@ -64,46 +50,52 @@ defmodule ClaudeCode do
64 50
65 51 ## Resume Previous Conversations
66 52
67 - # Get session ID from a previous interaction
68 - session_id = ClaudeCode.get_session_id(session)
53 + session_id = ClaudeCode.Session.session_id(session)
69 54
70 - # Later: resume the conversation
71 - {:ok, new_session} = ClaudeCode.start_link(
72 - api_key: "sk-ant-...",
73 - resume: session_id
74 - )
55 + {:ok, new_session} = ClaudeCode.start_link(resume: session_id)
75 56
76 57 ClaudeCode.stream(new_session, "Continue where we left off")
77 58 |> Enum.each(&IO.inspect/1)
78 59
79 - # Or fork to create a branch with a new session ID
80 - {:ok, forked} = ClaudeCode.start_link(
81 - resume: session_id,
82 - fork_session: true
83 - )
84 -
85 - See `ClaudeCode.Supervisor` for advanced supervision patterns.
60 + See `ClaudeCode.Session` for runtime configuration, MCP management,
61 + and introspection functions. See `ClaudeCode.Supervisor` for advanced
62 + supervision patterns.
86 63 """
87 64
65 + alias ClaudeCode.Adapter.Port.Installer
88 66 alias ClaudeCode.Message.ResultMessage
89 - alias ClaudeCode.Session
90 67
91 68 @doc """
92 69 Returns the SDK version string.
93 70
94 - Used internally for environment variables passed to the CLI subprocess.
95 -
96 71 ## Examples
97 72
98 73 iex> ClaudeCode.version()
99 - "0.29.0"
74 + "0.30.0"
100 75 """
101 76 @spec version() :: String.t()
102 77 def version do
103 78 :claude_code |> Application.spec(:vsn) |> to_string()
104 79 end
105 80
106 - @type session :: pid() | atom() | {:via, module(), any()}
81 + @doc """
82 + Returns the configured CLI version.
83 +
84 + This is the Claude Code CLI version the SDK is configured to use.
85 + It can be overridden via application config (`:cli_version`), otherwise
86 + defaults to the version the SDK was tested against.
87 +
88 + ## Examples
89 +
90 + iex> ClaudeCode.cli_version()
91 + "2.1.72"
92 + """
93 + @spec cli_version() :: String.t()
94 + def cli_version do
95 + Installer.configured_version()
96 + end
97 +
98 + @type session :: ClaudeCode.Session.session()
107 99 @type query_response ::
108 100 {:ok, ResultMessage.t()} | {:error, ResultMessage.t() | term()}
109 101 @type message_stream :: Enumerable.t(ClaudeCode.Message.t())
  @@ -153,7 +145,7 @@ defmodule ClaudeCode do
153 145 """
154 146 @spec start_link(keyword()) :: GenServer.on_start()
155 147 def start_link(opts \\ []) do
156 - Session.start_link(opts)
148 + ClaudeCode.Session.start_link(opts)
157 149 end
158 150
159 151 @doc """
  @@ -245,20 +237,7 @@ defmodule ClaudeCode do
245 237 """
246 238 @spec stream(session(), String.t(), keyword()) :: message_stream()
247 239 def stream(session, prompt, opts \\ []) do
248 - ClaudeCode.Stream.create(session, prompt, opts)
249 - end
250 -
251 - @doc """
252 - Returns the health status of the session's adapter.
253 -
254 - ## Examples
255 -
256 - :healthy = ClaudeCode.health(session)
257 - {:unhealthy, :port_dead} = ClaudeCode.health(session)
258 - """
259 - @spec health(session()) :: ClaudeCode.Adapter.health()
260 - def health(session) do
261 - GenServer.call(session, :health)
240 + ClaudeCode.Session.stream(session, prompt, opts)
262 241 end
263 242
264 243 @doc """
  @@ -272,202 +251,7 @@ defmodule ClaudeCode do
272 251 """
273 252 @spec stop(session()) :: :ok
274 253 def stop(session) do
275 - GenServer.stop(session)
276 - end
277 -
278 - @doc """
279 - Checks if a session is alive.
280 -
281 - ## Examples
282 -
283 - true = ClaudeCode.alive?(session)
284 - """
285 - @spec alive?(session()) :: boolean()
286 - def alive?(session) when is_atom(session) do
287 - case Process.whereis(session) do
288 - nil -> false
289 - pid -> Process.alive?(pid)
290 - end
291 - end
292 -
293 - def alive?(session) when is_pid(session) do
294 - Process.alive?(session)
295 - end
296 -
297 - @doc """
298 - Gets the current session ID for conversation continuity.
299 -
300 - Returns the session ID that Claude CLI is using to maintain conversation
301 - context. This ID is automatically captured from CLI responses and used
302 - for subsequent queries to continue the conversation.
303 -
304 - You can use this session ID with the `:resume` option when starting a
305 - new session to continue the conversation later, or with `:fork_session`
306 - to create a branch.
307 -
308 - ## Examples
309 -
310 - session_id = ClaudeCode.get_session_id(session)
311 - # => "abc123-session-id"
312 -
313 - # For a new session with no queries yet
314 - nil = ClaudeCode.get_session_id(session)
315 -
316 - # Resume later
317 - {:ok, new_session} = ClaudeCode.start_link(resume: session_id)
318 -
319 - # Or fork the conversation
320 - {:ok, forked} = ClaudeCode.start_link(resume: session_id, fork_session: true)
321 - """
322 - @spec get_session_id(session()) :: String.t() | nil
323 - def get_session_id(session) do
324 - GenServer.call(session, :get_session_id)
325 - end
326 -
327 - @doc """
328 - Clears the current session ID to start a fresh conversation.
329 -
330 - This will cause the next query to start a new conversation context
331 - rather than continuing the existing one. Useful when you want to
332 - reset the conversation history.
333 -
334 - ## Examples
335 -
336 - :ok = ClaudeCode.clear(session)
337 -
338 - # Next stream will start fresh
339 - ClaudeCode.stream(session, "Hello!")
340 - |> Enum.each(&IO.inspect/1)
341 - """
342 - @spec clear(session()) :: :ok
343 - def clear(session) do
344 - GenServer.call(session, :clear_session)
345 - end
346 -
347 - @doc """
348 - Changes the model mid-conversation.
349 -
350 - ## Examples
351 -
352 - {:ok, _} = ClaudeCode.set_model(session, "claude-sonnet-4-5-20250929")
353 - """
354 - @spec set_model(session(), String.t()) :: {:ok, map()} | {:error, term()}
355 - def set_model(session, model) do
356 - GenServer.call(session, {:control, :set_model, %{model: model}})
357 - end
358 -
359 - @doc """
360 - Changes the permission mode mid-conversation.
361 -
362 - ## Examples
363 -
364 - {:ok, _} = ClaudeCode.set_permission_mode(session, :bypass_permissions)
365 - """
366 - @spec set_permission_mode(session(), atom()) :: {:ok, map()} | {:error, term()}
367 - def set_permission_mode(session, mode) do
368 - GenServer.call(session, {:control, :set_permission_mode, %{mode: mode}})
369 - end
370 -
371 - @doc """
372 - Queries MCP server connection status.
373 -
374 - ## Examples
375 -
376 - {:ok, %{"servers" => servers}} = ClaudeCode.get_mcp_status(session)
377 - """
378 - @spec get_mcp_status(session()) :: {:ok, map()} | {:error, term()}
379 - def get_mcp_status(session) do
380 - GenServer.call(session, {:control, :mcp_status, %{}})
381 - end
382 -
383 - @doc """
384 - Gets server initialization info cached from the control handshake.
385 -
386 - ## Examples
387 -
388 - {:ok, info} = ClaudeCode.get_server_info(session)
389 - """
390 - @spec get_server_info(session()) :: {:ok, map() | nil} | {:error, term()}
391 - def get_server_info(session) do
392 - GenServer.call(session, :get_server_info)
393 - end
394 -
395 - @doc """
396 - Interrupts the current generation.
397 -
398 - Sends an interrupt signal to the CLI to stop the current generation.
399 - This is a fire-and-forget operation — the CLI will stop generating
400 - and emit a result message.
401 -
402 - ## Examples
403 -
404 - :ok = ClaudeCode.interrupt(session)
405 - """
406 - @spec interrupt(session()) :: :ok | {:error, term()}
407 - def interrupt(session) do
408 - GenServer.call(session, :interrupt)
409 - end
410 -
411 - @doc """
412 - Rewinds tracked files to the state at a specific user message checkpoint.
413 -
414 - ## Examples
415 -
416 - {:ok, _} = ClaudeCode.rewind_files(session, "user-msg-uuid-123")
417 - """
418 - @spec rewind_files(session(), String.t()) :: {:ok, map()} | {:error, term()}
419 - def rewind_files(session, user_message_id) do
420 - GenServer.call(session, {:control, :rewind_files, %{user_message_id: user_message_id}})
421 - end
422 -
423 - @doc """
424 - Reads conversation history from a session's JSONL file.
425 -
426 - Accepts either a session ID string or a running session reference.
427 - Returns user and assistant messages parsed into SDK message structs.
428 -
429 - ## Options
430 -
431 - - `:project_path` - Specific project path to search in (optional)
432 - - `:claude_dir` - Override the Claude directory (default: `~/.claude`)
433 -
434 - ## Examples
435 -
436 - # Read conversation history by session ID
437 - {:ok, messages} = ClaudeCode.conversation("abc123-def456")
438 -
439 - # Or from a running session
440 - {:ok, session} = ClaudeCode.start_link()
441 - ClaudeCode.query(session, "Hello!")
442 - {:ok, messages} = ClaudeCode.conversation(session)
443 -
444 - Enum.each(messages, fn
445 - %ClaudeCode.Message.UserMessage{message: %{content: content}} ->
446 - IO.puts("User: \#{inspect(content)}")
447 - %ClaudeCode.Message.AssistantMessage{message: %{content: blocks}} ->
448 - text = Enum.map_join(blocks, "", fn
449 - %ClaudeCode.Content.TextBlock{text: t} -> t
450 - _ -> ""
451 - end)
452 - IO.puts("Assistant: \#{text}")
453 - end)
454 -
455 - See `ClaudeCode.History` for more options.
456 - """
457 - @spec conversation(session() | String.t(), keyword()) ::
458 - {:ok, [ClaudeCode.Message.AssistantMessage.t() | ClaudeCode.Message.UserMessage.t()]}
459 - | {:error, term()}
460 - def conversation(session_or_id, opts \\ [])
461 -
462 - def conversation(session_id, opts) when is_binary(session_id) do
463 - ClaudeCode.History.conversation(session_id, opts)
464 - end
465 -
466 - def conversation(session, opts) do
467 - case get_session_id(session) do
468 - nil -> {:error, :no_session_id}
469 - session_id -> ClaudeCode.History.conversation(session_id, opts)
470 - end
254 + ClaudeCode.Session.stop(session)
471 255 end
472 256
473 257 # Private helpers
  @@ -29,7 +29,7 @@ defmodule ClaudeCode.Adapter.ControlHandler do
29 29 @spec handle_hook_callback(map(), HookRegistry.t()) :: map()
30 30 def handle_hook_callback(request, hook_registry) do
31 31 callback_id = request["callback_id"]
32 - input = atomize_keys(request["input"])
32 + input = ClaudeCode.MapUtils.safe_atomize_keys(request["input"])
33 33 tool_use_id = request["tool_use_id"]
34 34
35 35 case HookRegistry.lookup(hook_registry, callback_id) do
  @@ -61,13 +61,4 @@ defmodule ClaudeCode.Adapter.ControlHandler do
61 61 HookResponse.to_can_use_tool_wire(result)
62 62 end
63 63 end
64 -
65 - defp atomize_keys(map) when is_map(map) do
66 - Map.new(map, fn
67 - {key, value} when is_binary(key) -> {String.to_atom(key), value}
68 - {key, value} -> {key, value}
69 - end)
70 - end
71 -
72 - defp atomize_keys(other), do: other
73 64 end
Loading more files…