claude-agent-sdk 0.9.0 → 0.10.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +42 -4
- data/lib/claude_agent_sdk/message_parser.rb +2 -1
- data/lib/claude_agent_sdk/query.rb +4 -1
- data/lib/claude_agent_sdk/session_mutations.rb +165 -0
- data/lib/claude_agent_sdk/sessions.rb +4 -1
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +0 -3
- data/lib/claude_agent_sdk/types.rb +29 -4
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +17 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '03965bed10e6220b6f00d32ad789aa5c743c7c74f5625f046bd58c0137f1ccae'
|
|
4
|
+
data.tar.gz: c1460d6d10d52ddb5e0ece51e3b5eab2c6d5962d8a7ead7a07661e1ee63aaf99
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52e72c3cf52957c54965300787f213add70236025b7fd5de309aabba4fcb9243e1ff973141f4c0ae85aefddf3b182ac49a737a2696bd5f57893ca031af70999d
|
|
7
|
+
data.tar.gz: 3472c21997df7a77cf96eafd6f08671f77574321b23cbb8128d81364b2c523b64617c624c63f70a7a64f0bd6e1d3de807430bfd570e12ba989dda80163704417
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.10.0] - 2026-03-20
|
|
9
|
+
|
|
10
|
+
Port of Python SDK v0.1.48 features for feature parity.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
#### Session Mutations
|
|
15
|
+
- `ClaudeAgentSDK.rename_session(session_id:, title:, directory:)` — rename a session by appending a custom-title JSONL entry
|
|
16
|
+
- `ClaudeAgentSDK.tag_session(session_id:, tag:, directory:)` — tag a session (pass `nil` to clear); tags are Unicode-sanitized
|
|
17
|
+
- `SessionMutations` module with `rename_session`, `tag_session`, and internal Unicode sanitization helpers
|
|
18
|
+
- Ported from Python SDK's `_internal/session_mutations.py` with TOCTOU-safe `O_WRONLY | O_APPEND` file operations
|
|
19
|
+
|
|
20
|
+
#### AssistantMessage Usage
|
|
21
|
+
- `usage` attribute on `AssistantMessage` — token usage data from the API response
|
|
22
|
+
- `MessageParser` populates `usage` from `data.dig(:message, :usage)`
|
|
23
|
+
|
|
24
|
+
#### AgentDefinition Fields
|
|
25
|
+
- `skills`, `memory`, `mcp_servers` attributes on `AgentDefinition`
|
|
26
|
+
- Serialized as `skills`, `memory`, `mcpServers` (camelCase) in the CLI wire protocol initialize request
|
|
27
|
+
|
|
28
|
+
#### TaskUsage Typed Class
|
|
29
|
+
- `TaskUsage` class with `total_tokens`, `tool_uses`, `duration_ms` attributes
|
|
30
|
+
- `TaskUsage.from_hash` factory supporting symbol, string, camelCase, and snake_case keys
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
34
|
+
#### FGTS Environment Variable
|
|
35
|
+
- Removed auto-setting of `CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING` environment variable when `include_partial_messages` is enabled — Python SDK v0.1.48 reverted this because it causes HTTP 400 errors on LiteLLM proxies, Bedrock, and Vertex with Claude 4.5 models. The `--include-partial-messages` CLI flag remains the correct mechanism.
|
|
36
|
+
|
|
8
37
|
## [0.9.0] - 2026-03-12
|
|
9
38
|
|
|
10
39
|
Port of Python SDK v0.1.48 parity improvements.
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://badge.fury.io/rb/claude-agent-sdk)
|
|
8
8
|
|
|
9
|
-
### Feature Parity with Python SDK (v0.1.
|
|
9
|
+
### Feature Parity with Python SDK (v0.1.48)
|
|
10
10
|
|
|
11
11
|
| Feature | Python | Ruby |
|
|
12
12
|
|---------|:------:|:----:|
|
|
@@ -25,11 +25,13 @@
|
|
|
25
25
|
| Beta features (1M context) | ✅ | ✅ |
|
|
26
26
|
| File checkpointing & rewind | ✅ | ✅ |
|
|
27
27
|
| Session browsing (`list_sessions`, `get_session_messages`) | ✅ | ✅ |
|
|
28
|
+
| Session mutations (`rename_session`, `tag_session`) | ✅ | ✅ |
|
|
28
29
|
| Task message types (started/progress/notification) | ✅ | ✅ |
|
|
29
30
|
| MCP server control (reconnect/toggle/stop) | ✅ | ✅ |
|
|
30
31
|
| Subagent context on hook inputs | ✅ | ✅ |
|
|
31
32
|
| Typed MCP status response | ✅ | ✅ |
|
|
32
33
|
| `stop_reason` on `ResultMessage` | ✅ | ✅ |
|
|
34
|
+
| `usage` on `AssistantMessage` | ✅ | ✅ |
|
|
33
35
|
| Fallback model | ✅ | ✅ |
|
|
34
36
|
| Plugin support | ✅ | ✅ |
|
|
35
37
|
| Rails integration (configure block, ActionCable) | — | ✅ |
|
|
@@ -137,6 +139,7 @@ Both SDKs spawn `claude` CLI as a subprocess with stream-JSON over stdin/stdout.
|
|
|
137
139
|
- [Sandbox Settings](#sandbox-settings)
|
|
138
140
|
- [File Checkpointing & Rewind](#file-checkpointing--rewind)
|
|
139
141
|
- [Session Browsing](#session-browsing)
|
|
142
|
+
- [Session Mutations](#session-mutations)
|
|
140
143
|
- [Rails Integration](#rails-integration)
|
|
141
144
|
- [Types](#types)
|
|
142
145
|
- [Error Handling](#error-handling)
|
|
@@ -153,7 +156,7 @@ Add this line to your application's Gemfile:
|
|
|
153
156
|
gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
|
|
154
157
|
|
|
155
158
|
# Or use a stable version from RubyGems
|
|
156
|
-
gem 'claude-agent-sdk', '~> 0.
|
|
159
|
+
gem 'claude-agent-sdk', '~> 0.10.0'
|
|
157
160
|
```
|
|
158
161
|
|
|
159
162
|
And then execute:
|
|
@@ -934,6 +937,39 @@ Each `SessionMessage` includes `type` (`"user"` or `"assistant"`), `uuid`, `sess
|
|
|
934
937
|
|
|
935
938
|
> **Note:** Session browsing reads `~/.claude/projects/` JSONL files directly. It respects the `CLAUDE_CONFIG_DIR` environment variable and automatically detects git worktrees.
|
|
936
939
|
|
|
940
|
+
## Session Mutations
|
|
941
|
+
|
|
942
|
+
Rename or tag sessions programmatically — no CLI subprocess required.
|
|
943
|
+
|
|
944
|
+
### Renaming a Session
|
|
945
|
+
|
|
946
|
+
```ruby
|
|
947
|
+
# Rename a session (appends a custom-title JSONL entry)
|
|
948
|
+
ClaudeAgentSDK.rename_session(
|
|
949
|
+
session_id: '550e8400-e29b-41d4-a716-446655440000',
|
|
950
|
+
title: 'My refactoring session',
|
|
951
|
+
directory: '/path/to/project' # optional
|
|
952
|
+
)
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
### Tagging a Session
|
|
956
|
+
|
|
957
|
+
```ruby
|
|
958
|
+
# Tag a session (Unicode-sanitized before storing)
|
|
959
|
+
ClaudeAgentSDK.tag_session(
|
|
960
|
+
session_id: '550e8400-e29b-41d4-a716-446655440000',
|
|
961
|
+
tag: 'experiment'
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
# Clear a tag
|
|
965
|
+
ClaudeAgentSDK.tag_session(
|
|
966
|
+
session_id: '550e8400-e29b-41d4-a716-446655440000',
|
|
967
|
+
tag: nil
|
|
968
|
+
)
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
> **Note:** Session mutations use append-only JSONL writes with `O_WRONLY | O_APPEND` (no `O_CREAT`) for TOCTOU safety. They are safe to call while the session is open in a CLI process.
|
|
972
|
+
|
|
937
973
|
## Rails Integration
|
|
938
974
|
|
|
939
975
|
The SDK integrates well with Rails applications. Here are common patterns:
|
|
@@ -1120,7 +1156,8 @@ class AssistantMessage
|
|
|
1120
1156
|
attr_accessor :content, # Array<ContentBlock>
|
|
1121
1157
|
:model, # String
|
|
1122
1158
|
:parent_tool_use_id,# String | nil
|
|
1123
|
-
:error
|
|
1159
|
+
:error, # String | nil ('authentication_failed', 'billing_error', 'rate_limit', 'invalid_request', 'server_error', 'unknown')
|
|
1160
|
+
:usage # Hash | nil - Token usage info from the API response
|
|
1124
1161
|
end
|
|
1125
1162
|
```
|
|
1126
1163
|
|
|
@@ -1276,7 +1313,7 @@ end
|
|
|
1276
1313
|
| `HookMatcher` | Hook configuration with matcher pattern and timeout |
|
|
1277
1314
|
| `PermissionResultAllow` | Permission callback result to allow tool use |
|
|
1278
1315
|
| `PermissionResultDeny` | Permission callback result to deny tool use |
|
|
1279
|
-
| `AgentDefinition` | Agent definition with description, prompt, tools, model |
|
|
1316
|
+
| `AgentDefinition` | Agent definition with description, prompt, tools, model, skills, memory, mcp_servers |
|
|
1280
1317
|
| `ThinkingConfigAdaptive` | Adaptive thinking mode (32,000 token default budget) |
|
|
1281
1318
|
| `ThinkingConfigEnabled` | Enabled thinking with explicit `budget_tokens` |
|
|
1282
1319
|
| `ThinkingConfigDisabled` | Disabled thinking (0 tokens) |
|
|
@@ -1290,6 +1327,7 @@ end
|
|
|
1290
1327
|
| `McpServerInfo` | MCP server name and version |
|
|
1291
1328
|
| `McpToolInfo` | MCP tool name, description, and annotations |
|
|
1292
1329
|
| `McpToolAnnotations` | MCP tool annotation hints (`read_only`, `destructive`, `open_world`) |
|
|
1330
|
+
| `TaskUsage` | Typed usage data (`total_tokens`, `tool_uses`, `duration_ms`) with `from_hash` factory |
|
|
1293
1331
|
| `SDKSessionInfo` | Session metadata from `list_sessions` |
|
|
1294
1332
|
| `SessionMessage` | Single message from `get_session_messages` |
|
|
1295
1333
|
| `SandboxSettings` | Sandbox settings for isolated command execution |
|
|
@@ -61,7 +61,8 @@ module ClaudeAgentSDK
|
|
|
61
61
|
content: content_blocks,
|
|
62
62
|
model: data.dig(:message, :model),
|
|
63
63
|
parent_tool_use_id: data[:parent_tool_use_id],
|
|
64
|
-
error: data[:error] # authentication_failed, billing_error, rate_limit, invalid_request, server_error, unknown
|
|
64
|
+
error: data[:error], # authentication_failed, billing_error, rate_limit, invalid_request, server_error, unknown
|
|
65
|
+
usage: data.dig(:message, :usage)
|
|
65
66
|
)
|
|
66
67
|
end
|
|
67
68
|
|
|
@@ -89,7 +89,10 @@ module ClaudeAgentSDK
|
|
|
89
89
|
description: agent_def.description,
|
|
90
90
|
prompt: agent_def.prompt,
|
|
91
91
|
tools: agent_def.tools,
|
|
92
|
-
model: agent_def.model
|
|
92
|
+
model: agent_def.model,
|
|
93
|
+
skills: agent_def.skills,
|
|
94
|
+
memory: agent_def.memory,
|
|
95
|
+
mcpServers: agent_def.mcp_servers
|
|
93
96
|
}.compact
|
|
94
97
|
end
|
|
95
98
|
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative 'sessions'
|
|
5
|
+
|
|
6
|
+
module ClaudeAgentSDK
|
|
7
|
+
# Session mutation functions: rename and tag sessions.
|
|
8
|
+
#
|
|
9
|
+
# Ported from Python SDK's _internal/session_mutations.py.
|
|
10
|
+
# Appends typed metadata entries to the session's JSONL file,
|
|
11
|
+
# matching the CLI pattern. Safe to call from any SDK host process.
|
|
12
|
+
module SessionMutations
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Rename a session by appending a custom-title entry.
|
|
16
|
+
#
|
|
17
|
+
# list_sessions reads the LAST custom-title from the file tail, so
|
|
18
|
+
# repeated calls are safe — the most recent wins.
|
|
19
|
+
#
|
|
20
|
+
# @param session_id [String] UUID of the session to rename
|
|
21
|
+
# @param title [String] New session title (whitespace stripped)
|
|
22
|
+
# @param directory [String, nil] Project directory path
|
|
23
|
+
# @raise [ArgumentError] if session_id is invalid or title is empty
|
|
24
|
+
# @raise [Errno::ENOENT] if the session file cannot be found
|
|
25
|
+
def rename_session(session_id:, title:, directory: nil)
|
|
26
|
+
raise ArgumentError, "Invalid session_id: #{session_id}" unless session_id.match?(Sessions::UUID_RE)
|
|
27
|
+
|
|
28
|
+
stripped = title.strip
|
|
29
|
+
raise ArgumentError, 'title must be non-empty' if stripped.empty?
|
|
30
|
+
|
|
31
|
+
data = "#{JSON.generate({ type: 'custom-title', customTitle: stripped, sessionId: session_id },
|
|
32
|
+
space_size: 0)}\n"
|
|
33
|
+
|
|
34
|
+
append_to_session(session_id, data, directory)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Tag a session. Pass nil to clear the tag.
|
|
38
|
+
#
|
|
39
|
+
# Appends a {type:'tag',tag:<tag>,sessionId:<id>} JSONL entry.
|
|
40
|
+
# Tags are Unicode-sanitized before storing.
|
|
41
|
+
#
|
|
42
|
+
# @param session_id [String] UUID of the session to tag
|
|
43
|
+
# @param tag [String, nil] Tag string, or nil to clear
|
|
44
|
+
# @param directory [String, nil] Project directory path
|
|
45
|
+
# @raise [ArgumentError] if session_id is invalid or tag is empty after sanitization
|
|
46
|
+
# @raise [Errno::ENOENT] if the session file cannot be found
|
|
47
|
+
def tag_session(session_id:, tag:, directory: nil)
|
|
48
|
+
raise ArgumentError, "Invalid session_id: #{session_id}" unless session_id.match?(Sessions::UUID_RE)
|
|
49
|
+
|
|
50
|
+
if tag
|
|
51
|
+
sanitized = sanitize_unicode(tag).strip
|
|
52
|
+
raise ArgumentError, 'tag must be non-empty (use nil to clear)' if sanitized.empty?
|
|
53
|
+
|
|
54
|
+
tag = sanitized
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
data = "#{JSON.generate({ type: 'tag', tag: tag || '', sessionId: session_id },
|
|
58
|
+
space_size: 0)}\n"
|
|
59
|
+
|
|
60
|
+
append_to_session(session_id, data, directory)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# -- Private helpers --
|
|
64
|
+
|
|
65
|
+
def append_to_session(session_id, data, directory)
|
|
66
|
+
file_name = "#{session_id}.jsonl"
|
|
67
|
+
|
|
68
|
+
if directory
|
|
69
|
+
append_to_session_in_directory(session_id, data, file_name, directory)
|
|
70
|
+
else
|
|
71
|
+
append_to_session_global(session_id, data, file_name)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def append_to_session_in_directory(session_id, data, file_name, directory)
|
|
76
|
+
path = File.realpath(directory).unicode_normalize(:nfc)
|
|
77
|
+
|
|
78
|
+
# Try the exact/prefix-matched project directory first.
|
|
79
|
+
project_dir = Sessions.find_project_dir(path)
|
|
80
|
+
return if project_dir && try_append(File.join(project_dir, file_name), data)
|
|
81
|
+
|
|
82
|
+
# Worktree fallback
|
|
83
|
+
begin
|
|
84
|
+
worktree_paths = Sessions.detect_worktrees(path)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
worktree_paths = []
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
found = worktree_paths.any? do |wt_path|
|
|
90
|
+
next false if wt_path == path
|
|
91
|
+
|
|
92
|
+
wt_project_dir = Sessions.find_project_dir(wt_path)
|
|
93
|
+
wt_project_dir && try_append(File.join(wt_project_dir, file_name), data)
|
|
94
|
+
end
|
|
95
|
+
return if found
|
|
96
|
+
|
|
97
|
+
raise Errno::ENOENT, "Session #{session_id} not found in project directory for #{directory}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def append_to_session_global(session_id, data, file_name)
|
|
101
|
+
projects_dir = File.join(Sessions.config_dir, 'projects')
|
|
102
|
+
raise Errno::ENOENT, "Session #{session_id} not found (no projects directory)" unless File.directory?(projects_dir)
|
|
103
|
+
|
|
104
|
+
found = Dir.children(projects_dir).any? do |child|
|
|
105
|
+
candidate = File.join(projects_dir, child, file_name)
|
|
106
|
+
try_append(candidate, data)
|
|
107
|
+
end
|
|
108
|
+
return if found
|
|
109
|
+
|
|
110
|
+
raise Errno::ENOENT, "Session #{session_id} not found in any project directory"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Try appending to a path.
|
|
114
|
+
#
|
|
115
|
+
# Opens with WRONLY | APPEND (no CREAT) so the open fails with
|
|
116
|
+
# ENOENT if the file does not exist. Returns false for missing
|
|
117
|
+
# files or zero-byte files; true on successful write.
|
|
118
|
+
def try_append(path, data)
|
|
119
|
+
file = File.open(path, File::WRONLY | File::APPEND)
|
|
120
|
+
begin
|
|
121
|
+
return false if file.stat.size.zero? # rubocop:disable Style/ZeroLengthPredicate
|
|
122
|
+
|
|
123
|
+
file.write(data)
|
|
124
|
+
true
|
|
125
|
+
ensure
|
|
126
|
+
file.close
|
|
127
|
+
end
|
|
128
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Unicode sanitization — ported from Python SDK / TS sanitization.ts
|
|
133
|
+
#
|
|
134
|
+
# Iteratively applies NFKC normalization and strips format/private-use/
|
|
135
|
+
# unassigned characters until stable (max 10 iterations).
|
|
136
|
+
UNICODE_STRIP_RE = /[\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufeff\ue000-\uf8ff]/
|
|
137
|
+
FORMAT_CATEGORIES = %w[Cf Co Cn].freeze
|
|
138
|
+
|
|
139
|
+
def sanitize_unicode(value)
|
|
140
|
+
current = value
|
|
141
|
+
10.times do
|
|
142
|
+
previous = current
|
|
143
|
+
current = current.unicode_normalize(:nfkc)
|
|
144
|
+
current = current.each_char.reject { |c| FORMAT_CATEGORIES.include?(unicode_category(c)) }.join
|
|
145
|
+
current = current.gsub(UNICODE_STRIP_RE, '')
|
|
146
|
+
break if current == previous
|
|
147
|
+
end
|
|
148
|
+
current
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Returns the Unicode general category for a character (e.g., 'Cf', 'Lu', 'Ll').
|
|
152
|
+
def unicode_category(char)
|
|
153
|
+
# Ruby doesn't have a built-in unicodedata.category(), but we can
|
|
154
|
+
# check the specific categories we care about using regex properties.
|
|
155
|
+
return 'Cf' if char.match?(/\p{Cf}/)
|
|
156
|
+
return 'Co' if char.match?(/\p{Co}/)
|
|
157
|
+
return 'Cn' if char.match?(/\p{Cn}/)
|
|
158
|
+
|
|
159
|
+
'Other'
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private_class_method :append_to_session, :append_to_session_in_directory,
|
|
163
|
+
:append_to_session_global, :try_append, :sanitize_unicode, :unicode_category
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -491,9 +491,12 @@ module ClaudeAgentSDK
|
|
|
491
491
|
end
|
|
492
492
|
|
|
493
493
|
private_class_method :list_sessions_for_directory, :list_all_sessions,
|
|
494
|
-
:deduplicate_sessions,
|
|
494
|
+
:deduplicate_sessions,
|
|
495
495
|
:find_session_file, :parse_jsonl_entries,
|
|
496
496
|
:build_conversation_chain, :walk_to_leaf, :walk_to_root,
|
|
497
497
|
:filter_visible_messages, :read_head_tail, :build_session_info
|
|
498
|
+
|
|
499
|
+
# These remain accessible for SessionMutations:
|
|
500
|
+
# config_dir, sanitize_path, find_project_dir, detect_worktrees
|
|
498
501
|
end
|
|
499
502
|
end
|
|
@@ -181,9 +181,6 @@ module ClaudeAgentSDK
|
|
|
181
181
|
process_env = ENV.to_h.merge('CLAUDECODE' => nil, 'CLAUDE_AGENT_SDK_VERSION' => VERSION).merge(custom_env)
|
|
182
182
|
process_env['CLAUDE_CODE_ENTRYPOINT'] ||= 'sdk-rb'
|
|
183
183
|
process_env['CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING'] = 'true' if @options.enable_file_checkpointing
|
|
184
|
-
if @options.include_partial_messages
|
|
185
|
-
process_env['CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING'] ||= '1'
|
|
186
|
-
end
|
|
187
184
|
process_env['PWD'] = @cwd.to_s if @cwd
|
|
188
185
|
|
|
189
186
|
# Determine stderr handling
|
|
@@ -104,13 +104,14 @@ module ClaudeAgentSDK
|
|
|
104
104
|
|
|
105
105
|
# Assistant message with content blocks
|
|
106
106
|
class AssistantMessage
|
|
107
|
-
attr_accessor :content, :model, :parent_tool_use_id, :error
|
|
107
|
+
attr_accessor :content, :model, :parent_tool_use_id, :error, :usage
|
|
108
108
|
|
|
109
|
-
def initialize(content:, model:, parent_tool_use_id: nil, error: nil)
|
|
109
|
+
def initialize(content:, model:, parent_tool_use_id: nil, error: nil, usage: nil)
|
|
110
110
|
@content = content
|
|
111
111
|
@model = model
|
|
112
112
|
@parent_tool_use_id = parent_tool_use_id
|
|
113
113
|
@error = error # One of: authentication_failed, billing_error, rate_limit, invalid_request, server_error, unknown
|
|
114
|
+
@usage = usage # Token usage info from the API response
|
|
114
115
|
end
|
|
115
116
|
end
|
|
116
117
|
|
|
@@ -127,6 +128,27 @@ module ClaudeAgentSDK
|
|
|
127
128
|
# Task lifecycle notification statuses
|
|
128
129
|
TASK_NOTIFICATION_STATUSES = %w[completed failed stopped].freeze
|
|
129
130
|
|
|
131
|
+
# Typed usage data for task progress and notifications
|
|
132
|
+
class TaskUsage
|
|
133
|
+
attr_accessor :total_tokens, :tool_uses, :duration_ms
|
|
134
|
+
|
|
135
|
+
def initialize(total_tokens: 0, tool_uses: 0, duration_ms: 0)
|
|
136
|
+
@total_tokens = total_tokens
|
|
137
|
+
@tool_uses = tool_uses
|
|
138
|
+
@duration_ms = duration_ms
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.from_hash(hash)
|
|
142
|
+
return nil unless hash.is_a?(Hash)
|
|
143
|
+
|
|
144
|
+
new(
|
|
145
|
+
total_tokens: hash[:total_tokens] || hash['total_tokens'] || hash[:totalTokens] || hash['totalTokens'] || 0,
|
|
146
|
+
tool_uses: hash[:tool_uses] || hash['tool_uses'] || hash[:toolUses] || hash['toolUses'] || 0,
|
|
147
|
+
duration_ms: hash[:duration_ms] || hash['duration_ms'] || hash[:durationMs] || hash['durationMs'] || 0
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
130
152
|
# Task started system message (subagent/background task started)
|
|
131
153
|
class TaskStartedMessage < SystemMessage
|
|
132
154
|
attr_accessor :task_id, :description, :uuid, :session_id, :tool_use_id, :task_type
|
|
@@ -285,13 +307,16 @@ module ClaudeAgentSDK
|
|
|
285
307
|
|
|
286
308
|
# Agent definition configuration
|
|
287
309
|
class AgentDefinition
|
|
288
|
-
attr_accessor :description, :prompt, :tools, :model
|
|
310
|
+
attr_accessor :description, :prompt, :tools, :model, :skills, :memory, :mcp_servers
|
|
289
311
|
|
|
290
|
-
def initialize(description:, prompt:, tools: nil, model: nil)
|
|
312
|
+
def initialize(description:, prompt:, tools: nil, model: nil, skills: nil, memory: nil, mcp_servers: nil)
|
|
291
313
|
@description = description
|
|
292
314
|
@prompt = prompt
|
|
293
315
|
@tools = tools
|
|
294
316
|
@model = model
|
|
317
|
+
@skills = skills # Array of skill names
|
|
318
|
+
@memory = memory # One of: 'user', 'project', 'local'
|
|
319
|
+
@mcp_servers = mcp_servers # Array of server names or config hashes
|
|
295
320
|
end
|
|
296
321
|
end
|
|
297
322
|
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative 'claude_agent_sdk/query'
|
|
|
11
11
|
require_relative 'claude_agent_sdk/sdk_mcp_server'
|
|
12
12
|
require_relative 'claude_agent_sdk/streaming'
|
|
13
13
|
require_relative 'claude_agent_sdk/sessions'
|
|
14
|
+
require_relative 'claude_agent_sdk/session_mutations'
|
|
14
15
|
require 'async'
|
|
15
16
|
require 'securerandom'
|
|
16
17
|
|
|
@@ -78,6 +79,22 @@ module ClaudeAgentSDK
|
|
|
78
79
|
Sessions.get_session_messages(session_id: session_id, directory: directory, limit: limit, offset: offset)
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
# Rename a session by appending a custom-title entry
|
|
83
|
+
# @param session_id [String] UUID of the session to rename
|
|
84
|
+
# @param title [String] New session title
|
|
85
|
+
# @param directory [String, nil] Project directory path
|
|
86
|
+
def self.rename_session(session_id:, title:, directory: nil)
|
|
87
|
+
SessionMutations.rename_session(session_id: session_id, title: title, directory: directory)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Tag a session. Pass nil to clear the tag.
|
|
91
|
+
# @param session_id [String] UUID of the session to tag
|
|
92
|
+
# @param tag [String, nil] Tag string, or nil to clear
|
|
93
|
+
# @param directory [String, nil] Project directory path
|
|
94
|
+
def self.tag_session(session_id:, tag:, directory: nil)
|
|
95
|
+
SessionMutations.tag_session(session_id: session_id, tag: tag, directory: directory)
|
|
96
|
+
end
|
|
97
|
+
|
|
81
98
|
def self.query(prompt:, options: nil, &block)
|
|
82
99
|
return enum_for(:query, prompt: prompt, options: options) unless block
|
|
83
100
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-03-
|
|
10
|
+
date: 2026-03-20 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|
|
@@ -109,6 +109,7 @@ files:
|
|
|
109
109
|
- lib/claude_agent_sdk/message_parser.rb
|
|
110
110
|
- lib/claude_agent_sdk/query.rb
|
|
111
111
|
- lib/claude_agent_sdk/sdk_mcp_server.rb
|
|
112
|
+
- lib/claude_agent_sdk/session_mutations.rb
|
|
112
113
|
- lib/claude_agent_sdk/sessions.rb
|
|
113
114
|
- lib/claude_agent_sdk/streaming.rb
|
|
114
115
|
- lib/claude_agent_sdk/subprocess_cli_transport.rb
|