claude-agent-sdk 1.4.2 → 1.4.3
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 +11 -0
- data/docs/options.md +1 -1
- data/lib/claude_agent_sdk/cli_installer.rb +1 -1
- data/lib/claude_agent_sdk/sessions.rb +142 -37
- data/lib/claude_agent_sdk/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb80b64b04c7a08624103b5ebf09504a42f20bd42d8587e23a10761dffa54d7e
|
|
4
|
+
data.tar.gz: 80dbdc35d394a22bbe20f5db952044cf82668e5c12c3eb98de2cb454779f6741
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 355f4fd5d1163e46b922be4c8320b6d3ede6a916d7dd2b732aa70dc955d924411e0cb19b87b86b76c1a2cd4cdd3fc0d4fc1599605b60b6b88b7cbb70518c3f1d
|
|
7
|
+
data.tar.gz: a9e98c5026f9b04ac4a50b39c665cebc6e6b29058a8db8050a6293fc3aa49044c20cb5a48bddf3b6ccd0636031a79a86377b95fca8f684b2af2bb909cd9c558b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.3] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
Syncs with Python SDK 0.2.174 (previously 0.2.162). Python #1331 and #1332 were already in 1.2.0, and `SandboxSettings#ignore_violations` already took the command-pattern Hash that Python #1380 fixed its type to; every other release in the range only bumps the CLI Python bundles.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **`get_session_messages` and `get_subagent_messages` (disk and `session_store:`) no longer drop the tool calls of a parallel batch** (Python SDK #1422). The conversation goes on from the tool result written last. When that result answers an earlier call of the batch than the last one, the `tool_use` entries written after that call were off the path the readers walk, so 1.2.0's fix (#157) never saw them: the readers returned neither those calls nor their results. Of the parallel batches in one developer's local transcripts, about 7% have this shape. The readers now put back every entry of an API message on the conversation, right after its last entry there (in timestamp order), then the results as before. A result that the CLI parented on a hook attachment instead of its call's entry (its `sourceToolAssistantUUID` still names the call) is put back as well, when the same agent wrote both. The guards from #157 hold: the chain's own result wins, at most one result comes back per call, and the result of a branch a rewind dropped stays out. That last guard was also too wide on real transcripts, and missed results #157 meant to return: going on from a result no longer counts as a dropped branch when what goes on is a later call of the same batch, another result of the batch (the CLI sometimes parents one under an earlier result's hook attachment), or a meta entry (the `[Image: ...]` note the CLI writes under a screenshot result). A branch that goes on through a meta entry or through another result of the batch is still recognized. Affected sessions return more messages than before, so `offset:`/`limit:` pages over them shift. Python fixes the main-session readers only; here both readers are fixed.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `docs/options.md`: `include_hook_events` does not turn off the `SessionStart` and `Setup` hook events, which the CLI sends whether or not it is set (Python SDK #1336).
|
|
19
|
+
- `CLIInstaller::PINNED_CLI_VERSION` moves from 2.1.295 to **2.1.311**, following the CLI the Python SDK bundles. `CLIInstaller.install_pinned` installs it. See the [Claude Code changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md).
|
|
20
|
+
|
|
10
21
|
## [1.4.2] - 2026-10-27
|
|
11
22
|
|
|
12
23
|
### Fixed
|
data/docs/options.md
CHANGED
|
@@ -158,7 +158,7 @@ Asks the CLI for partial message chunks while the model is still producing a mes
|
|
|
158
158
|
|
|
159
159
|
### `include_hook_events`
|
|
160
160
|
|
|
161
|
-
Asks the CLI to put all hook lifecycle events into the message stream, as `HookStartedMessage`, `HookProgressMessage` and `HookResponseMessage` (see [System and progress messages](types.md#system-and-progress-messages)). It only adds messages: the callbacks you register with `hooks` are called whether or not it is set.
|
|
161
|
+
Asks the CLI to put all hook lifecycle events into the message stream, as `HookStartedMessage`, `HookProgressMessage` and `HookResponseMessage` (see [System and progress messages](types.md#system-and-progress-messages)). It only adds messages: the callbacks you register with `hooks` are called whether or not it is set. It does not turn any off either: the CLI sends the events of `SessionStart` and `Setup` hooks whether or not it is set.
|
|
162
162
|
|
|
163
163
|
## The CLI process
|
|
164
164
|
|
|
@@ -53,7 +53,7 @@ module ClaudeAgentSDK
|
|
|
53
53
|
# Single source of truth: bumped here (and only here) by
|
|
54
54
|
# .github/workflows/cli-pin-bump.yml or a Python-sync release, so a
|
|
55
55
|
# Dependabot bump of the gem carries the CLI forward with it.
|
|
56
|
-
PINNED_CLI_VERSION = '2.1.
|
|
56
|
+
PINNED_CLI_VERSION = '2.1.311'
|
|
57
57
|
# @api private
|
|
58
58
|
BINARY_NAME = 'claude'
|
|
59
59
|
# @api private
|
|
@@ -2037,25 +2037,29 @@ module ClaudeAgentSDK
|
|
|
2037
2037
|
%w[user assistant].include?(entry['type']) && !off_main_conversation?(entry)
|
|
2038
2038
|
end
|
|
2039
2039
|
|
|
2040
|
-
# Put the results of parallel tool calls back on a
|
|
2040
|
+
# Put the tool calls and results of parallel tool calls back on a
|
|
2041
|
+
# leaf-to-root chain.
|
|
2041
2042
|
#
|
|
2042
|
-
# The CLI writes one assistant entry per
|
|
2043
|
-
#
|
|
2044
|
-
# tool_use. With two or more calls in one API message
|
|
2045
|
-
# the conversation
|
|
2046
|
-
#
|
|
2047
|
-
#
|
|
2043
|
+
# The CLI writes one assistant entry per content block (they share
|
|
2044
|
+
# message.id) and parents every tool_result on the entry that holds ITS
|
|
2045
|
+
# tool_use. With two or more calls in one API message the transcript
|
|
2046
|
+
# forks: the conversation goes on from the result written last, and a
|
|
2047
|
+
# single-path walk keeps only that branch. When that result answers an
|
|
2048
|
+
# earlier call, the tool_use entries written after it are off the path
|
|
2049
|
+
# too, results and all (Python #1422).
|
|
2048
2050
|
#
|
|
2049
|
-
#
|
|
2050
|
-
#
|
|
2051
|
-
#
|
|
2052
|
-
#
|
|
2053
|
-
#
|
|
2054
|
-
# result
|
|
2055
|
-
#
|
|
2056
|
-
#
|
|
2051
|
+
# So first the off-chain entries of every API message on the chain go
|
|
2052
|
+
# back in, right after that message's last entry on the chain
|
|
2053
|
+
# (insert_parallel_siblings). Then, for each assistant entry, the results
|
|
2054
|
+
# off the chain that answer one of its calls are inserted — in file order
|
|
2055
|
+
# — before the next user entry of the chain (the batch's own on-chain
|
|
2056
|
+
# result), or at the end when the chain has none. The anchor, not the raw
|
|
2057
|
+
# file position, decides the place: a result that arrived after the
|
|
2058
|
+
# conversation had already moved on to a further tool_use of the same
|
|
2059
|
+
# message still lands with its batch, so every result follows the
|
|
2060
|
+
# assistant turn that asked for it.
|
|
2057
2061
|
#
|
|
2058
|
-
# The tool_use_id match is what keeps other user
|
|
2062
|
+
# The tool_use_id match is what keeps other user entries out: a prompt
|
|
2059
2063
|
# abandoned by a rewind is a second child of a chain entry too, and
|
|
2060
2064
|
# starts a branch that was dropped — and so is the old result of a call
|
|
2061
2065
|
# the conversation was rewound to and answered again: the chain's own
|
|
@@ -2067,6 +2071,7 @@ module ClaudeAgentSDK
|
|
|
2067
2071
|
# rejects sidechain / meta / team entries (main transcripts; a subagent
|
|
2068
2072
|
# transcript is sidechain throughout).
|
|
2069
2073
|
def reattach_parallel_tool_results(chain, entries, skip_flagged:)
|
|
2074
|
+
chain = insert_parallel_siblings(chain, entries, skip_flagged)
|
|
2070
2075
|
off_chain = off_chain_tool_results(chain, entries, skip_flagged)
|
|
2071
2076
|
return chain if off_chain.empty?
|
|
2072
2077
|
|
|
@@ -2083,29 +2088,79 @@ module ClaudeAgentSDK
|
|
|
2083
2088
|
placed.concat(pending.sort_by(&:first).map(&:last))
|
|
2084
2089
|
end
|
|
2085
2090
|
|
|
2086
|
-
#
|
|
2087
|
-
#
|
|
2088
|
-
|
|
2091
|
+
# The chain with the off-chain assistant entries of each API message on
|
|
2092
|
+
# it inserted after that message's last entry on the chain, in timestamp
|
|
2093
|
+
# order (file order on a tie, as Python's stable sort). Only a message
|
|
2094
|
+
# the chain holds is completed: an earlier attempt at the same turn is a
|
|
2095
|
+
# different API message and stays out.
|
|
2096
|
+
def insert_parallel_siblings(chain, entries, skip_flagged)
|
|
2097
|
+
recovered = off_chain_siblings(chain, entries, skip_flagged)
|
|
2098
|
+
return chain if recovered.empty?
|
|
2099
|
+
|
|
2100
|
+
chain.flat_map do |entry|
|
|
2101
|
+
siblings = recovered.fetch(entry['uuid'], {}).values
|
|
2102
|
+
[entry, *siblings.sort_by { |timestamp, position, _| [timestamp, position] }.map(&:last)]
|
|
2103
|
+
end
|
|
2104
|
+
end
|
|
2105
|
+
|
|
2106
|
+
# { uuid of an API message's last entry on the chain => { uuid => [timestamp, file position, entry] } }
|
|
2107
|
+
# for that message's entries off the chain.
|
|
2108
|
+
def off_chain_siblings(chain, entries, skip_flagged)
|
|
2109
|
+
anchors = {} # message.id => uuid of its last entry on the chain
|
|
2110
|
+
chain.each do |entry|
|
|
2111
|
+
message_id = assistant_message_id(entry)
|
|
2112
|
+
anchors[message_id] = entry['uuid'] if message_id
|
|
2113
|
+
end
|
|
2114
|
+
return {} if anchors.empty?
|
|
2115
|
+
|
|
2116
|
+
on_chain = chain.to_set { |entry| entry['uuid'] }
|
|
2117
|
+
recovered = {}
|
|
2118
|
+
entries.each_with_index do |entry, position|
|
|
2119
|
+
anchor = anchors[assistant_message_id(entry)]
|
|
2120
|
+
next if anchor.nil? || on_chain.include?(entry['uuid']) || (skip_flagged && off_main_conversation?(entry))
|
|
2121
|
+
|
|
2122
|
+
# A store can hold a second copy of an entry (a retried mirror batch).
|
|
2123
|
+
(recovered[anchor] ||= {})[entry['uuid']] ||= [entry['timestamp'].to_s, position, entry]
|
|
2124
|
+
end
|
|
2125
|
+
recovered
|
|
2126
|
+
end
|
|
2127
|
+
|
|
2128
|
+
# { uuid of the assistant entry a result belongs to => [[file position, entry], ...] }
|
|
2129
|
+
# for the off-chain results reattach_parallel_tool_results places. A
|
|
2130
|
+
# result belongs to its parent when that is an assistant entry of the
|
|
2131
|
+
# chain, else to the entry holding the call it answers, if the same agent
|
|
2132
|
+
# wrote both: the CLI sometimes parents a result on a hook attachment
|
|
2133
|
+
# (its sourceToolAssistantUUID still names the call).
|
|
2134
|
+
def off_chain_tool_results(chain, entries, skip_flagged) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- one filter per condition of the re-attachment rule
|
|
2089
2135
|
on_chain = Set.new
|
|
2090
2136
|
assistants = Set.new
|
|
2091
|
-
|
|
2137
|
+
owners = {} # tool_use id => the chain's assistant entry holding it (nil when two API messages claim it)
|
|
2092
2138
|
answered = Set.new # tool_use ids the chain's own results answer
|
|
2093
2139
|
chain.each do |entry|
|
|
2094
2140
|
on_chain << entry['uuid']
|
|
2095
2141
|
case entry['type']
|
|
2096
2142
|
when 'assistant'
|
|
2097
2143
|
assistants << entry['uuid']
|
|
2098
|
-
|
|
2144
|
+
content_block_values(entry, 'tool_use', 'id').each do |id|
|
|
2145
|
+
owner = owners.fetch(id, entry)
|
|
2146
|
+
owners[id] = owner && assistant_message_id(owner) == assistant_message_id(entry) ? entry : nil
|
|
2147
|
+
end
|
|
2099
2148
|
when 'user' then answered.merge(content_block_values(entry, 'tool_result', 'tool_use_id'))
|
|
2100
2149
|
end
|
|
2101
2150
|
end
|
|
2102
|
-
return {} if (
|
|
2151
|
+
return {} if (owners.keys - answered.to_a).empty?
|
|
2103
2152
|
|
|
2104
|
-
|
|
2153
|
+
# An entry of the chain going on from a result (the next tool_use of
|
|
2154
|
+
# its batch) is not a branch that went on. Nor is another result for a
|
|
2155
|
+
# call of the chain (the CLI sometimes parents one under an earlier
|
|
2156
|
+
# result's hook attachment) — unless something went on from it in turn.
|
|
2157
|
+
batch = entries.filter_map do |entry|
|
|
2158
|
+
entry['uuid'] if entry['type'] == 'user' && answers_chain_call?(entry, owners)
|
|
2159
|
+
end
|
|
2160
|
+
continued = continued_from(entries, except: on_chain, relay: batch.to_set)
|
|
2105
2161
|
found = {}
|
|
2106
2162
|
entries.each_with_index do |entry, position|
|
|
2107
|
-
next unless entry['type'] == 'user' &&
|
|
2108
|
-
next if on_chain.include?(entry['uuid'])
|
|
2163
|
+
next unless entry['type'] == 'user' && !on_chain.include?(entry['uuid'])
|
|
2109
2164
|
next if skip_flagged && off_main_conversation?(entry)
|
|
2110
2165
|
# A result something went on from started a branch that a rewind
|
|
2111
2166
|
# dropped (the conversation was rewound to the call and went on
|
|
@@ -2114,13 +2169,17 @@ module ClaudeAgentSDK
|
|
|
2114
2169
|
next if continued.include?(entry['uuid'])
|
|
2115
2170
|
|
|
2116
2171
|
ids = content_block_values(entry, 'tool_result', 'tool_use_id')
|
|
2117
|
-
next if
|
|
2172
|
+
next if !answers_chain_call?(entry, owners) || ids.any? { |id| answered.include?(id) }
|
|
2173
|
+
|
|
2174
|
+
anchor = entry['parentUuid'] if assistants.include?(entry['parentUuid'])
|
|
2175
|
+
anchor ||= (owner = owners[ids.first]) && same_writer?(entry, owner) && owner['uuid']
|
|
2176
|
+
next unless anchor
|
|
2118
2177
|
|
|
2119
2178
|
# Claimed: a later result for the same call is not added — nor a
|
|
2120
2179
|
# second copy of this entry, which a store can hold (a retried mirror
|
|
2121
2180
|
# batch overlaps the write it retries).
|
|
2122
2181
|
answered.merge(ids)
|
|
2123
|
-
(found[
|
|
2182
|
+
(found[anchor] ||= []) << [position, entry]
|
|
2124
2183
|
end
|
|
2125
2184
|
found
|
|
2126
2185
|
end
|
|
@@ -2128,21 +2187,64 @@ module ClaudeAgentSDK
|
|
|
2128
2187
|
# uuids of the entries a user or assistant entry continues from: its
|
|
2129
2188
|
# nearest user / assistant ancestor, reached through entries that are
|
|
2130
2189
|
# neither (hook attachments, system entries) — so a hook attachment
|
|
2131
|
-
# written after an entry does not count as going on from it.
|
|
2132
|
-
|
|
2190
|
+
# written after an entry does not count as going on from it. Meta
|
|
2191
|
+
# entries count as neither: the CLI writes one under a result itself
|
|
2192
|
+
# (the "[Image: ...]" note of a screenshot), so it does not go on from
|
|
2193
|
+
# the result, and a branch that goes on through one is still found.
|
|
2194
|
+
# Entries whose uuid is in +except+ are not counted as going on; those in
|
|
2195
|
+
# +relay+ only once something goes on from them.
|
|
2196
|
+
def continued_from(entries, except: Set.new, relay: Set.new)
|
|
2197
|
+
parents = goes_on_from(entries, except)
|
|
2198
|
+
continued = Set.new
|
|
2199
|
+
pending = parents.keys.reject { |uuid| relay.include?(uuid) }
|
|
2200
|
+
while (uuid = pending.pop)
|
|
2201
|
+
from = parents[uuid]
|
|
2202
|
+
pending << from if from && continued.add?(from) && relay.include?(from)
|
|
2203
|
+
end
|
|
2204
|
+
continued
|
|
2205
|
+
end
|
|
2206
|
+
|
|
2207
|
+
# { uuid => uuid of the entry it goes on from } for the user and
|
|
2208
|
+
# assistant entries not in +except+ (see continued_from).
|
|
2209
|
+
def goes_on_from(entries, except)
|
|
2133
2210
|
by_uuid = {}
|
|
2134
2211
|
entries.each { |entry| by_uuid[entry['uuid']] = entry if entry['uuid'] }
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
next unless %w[user assistant].include?(entry['type'])
|
|
2212
|
+
entries.each_with_object({}) do |entry, found|
|
|
2213
|
+
next if !goes_on?(entry) || except.include?(entry['uuid'])
|
|
2138
2214
|
|
|
2139
2215
|
seen = Set.new
|
|
2140
2216
|
parent = by_uuid[entry['parentUuid']]
|
|
2141
|
-
parent = by_uuid[parent['parentUuid']] while parent &&
|
|
2142
|
-
|
|
2143
|
-
continued << parent['uuid'] if parent && %w[user assistant].include?(parent['type'])
|
|
2217
|
+
parent = by_uuid[parent['parentUuid']] while parent && !goes_on?(parent) && seen.add?(parent['uuid'])
|
|
2218
|
+
found[entry['uuid']] = parent['uuid'] if parent && goes_on?(parent)
|
|
2144
2219
|
end
|
|
2145
|
-
|
|
2220
|
+
end
|
|
2221
|
+
|
|
2222
|
+
# A user or assistant entry that is not a meta injection: what
|
|
2223
|
+
# continued_from counts as going on.
|
|
2224
|
+
def goes_on?(entry)
|
|
2225
|
+
%w[user assistant].include?(entry['type']) && !entry['isMeta']
|
|
2226
|
+
end
|
|
2227
|
+
|
|
2228
|
+
# Whether an entry carries tool_results, every one for a call +owners+
|
|
2229
|
+
# (tool_use id => the chain's entry holding it) places.
|
|
2230
|
+
def answers_chain_call?(entry, owners)
|
|
2231
|
+
ids = content_block_values(entry, 'tool_result', 'tool_use_id')
|
|
2232
|
+
!ids.empty? && ids.all? { |id| owners[id] }
|
|
2233
|
+
end
|
|
2234
|
+
|
|
2235
|
+
# The API message.id of an assistant entry (nil for any other entry).
|
|
2236
|
+
def assistant_message_id(entry)
|
|
2237
|
+
return unless entry['type'] == 'assistant'
|
|
2238
|
+
|
|
2239
|
+
message = entry['message']
|
|
2240
|
+
message_id = message['id'] if message.is_a?(Hash)
|
|
2241
|
+
message_id if message_id.is_a?(String) && !message_id.empty?
|
|
2242
|
+
end
|
|
2243
|
+
|
|
2244
|
+
# Whether two entries were written by the same agent (the main session,
|
|
2245
|
+
# or one subagent).
|
|
2246
|
+
def same_writer?(entry, other)
|
|
2247
|
+
!entry['isSidechain'] == !other['isSidechain'] && entry['agentId'] == other['agentId']
|
|
2146
2248
|
end
|
|
2147
2249
|
|
|
2148
2250
|
# Values of +key+ over the +type+ content blocks of an entry's message
|
|
@@ -2208,7 +2310,10 @@ module ClaudeAgentSDK
|
|
|
2208
2310
|
:collect_agent_files, :parse_jsonl_entries, :utf8_transcript_text,
|
|
2209
2311
|
:build_conversation_chain, :walk_to_leaf, :walk_to_root,
|
|
2210
2312
|
:pick_leaf, :visible_ancestor?, :off_main_conversation?, :visible_message?,
|
|
2211
|
-
:reattach_parallel_tool_results, :
|
|
2313
|
+
:reattach_parallel_tool_results, :insert_parallel_siblings, :off_chain_siblings,
|
|
2314
|
+
:off_chain_tool_results,
|
|
2315
|
+
:continued_from, :goes_on_from, :goes_on?,
|
|
2316
|
+
:answers_chain_call?, :assistant_message_id, :same_writer?,
|
|
2212
2317
|
:content_block_values,
|
|
2213
2318
|
:filter_visible_messages, :build_session_info, :created_at_from_file, :user_entry_texts,
|
|
2214
2319
|
:each_line_past_head,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ya-luotao
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async
|