Packages
claude_code
0.30.0
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.1
0.32.2
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Claude Agent SDK for Elixir – Build AI agents with Claude Code
Current section
46 Versions
Jump to
Current section
46 Versions
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…