claude-agent-sdk 0.16.9 → 0.17.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 +1 -1
- data/docs/sessions.md +79 -0
- data/lib/claude_agent_sdk/command_builder.rb +4 -0
- data/lib/claude_agent_sdk/fiber_boundary.rb +14 -2
- data/lib/claude_agent_sdk/message_parser.rb +1 -0
- data/lib/claude_agent_sdk/query.rb +62 -8
- data/lib/claude_agent_sdk/session_mutations.rb +248 -63
- data/lib/claude_agent_sdk/session_resume.rb +444 -0
- data/lib/claude_agent_sdk/session_store.rb +353 -0
- data/lib/claude_agent_sdk/session_summary.rb +188 -0
- data/lib/claude_agent_sdk/sessions.rb +460 -7
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +84 -0
- data/lib/claude_agent_sdk/testing/session_store_conformance.rb +295 -0
- data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +218 -0
- data/lib/claude_agent_sdk/types.rb +43 -11
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +292 -62
- metadata +9 -4
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -12,6 +12,10 @@ require_relative 'claude_agent_sdk/query'
|
|
|
12
12
|
require_relative 'claude_agent_sdk/sdk_mcp_server'
|
|
13
13
|
require_relative 'claude_agent_sdk/streaming'
|
|
14
14
|
require_relative 'claude_agent_sdk/sessions'
|
|
15
|
+
require_relative 'claude_agent_sdk/session_summary'
|
|
16
|
+
require_relative 'claude_agent_sdk/session_store'
|
|
17
|
+
require_relative 'claude_agent_sdk/transcript_mirror_batcher'
|
|
18
|
+
require_relative 'claude_agent_sdk/session_resume'
|
|
15
19
|
require_relative 'claude_agent_sdk/session_mutations'
|
|
16
20
|
require_relative 'claude_agent_sdk/fiber_boundary'
|
|
17
21
|
require 'async'
|
|
@@ -141,6 +145,107 @@ module ClaudeAgentSDK
|
|
|
141
145
|
up_to_message_id: up_to_message_id, title: title)
|
|
142
146
|
end
|
|
143
147
|
|
|
148
|
+
# Derive the SessionStore +project_key+ for a directory (default: cwd).
|
|
149
|
+
# Matches the CLI's project-directory naming so keys align between local-disk
|
|
150
|
+
# and store-mirrored transcripts.
|
|
151
|
+
# @param directory [String, Pathname, nil] Directory to key (nil = cwd)
|
|
152
|
+
# @return [String] The project key
|
|
153
|
+
def self.project_key_for_directory(directory = nil)
|
|
154
|
+
Sessions.project_key_for_directory(directory)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Fold a batch of appended transcript entries into a running session summary.
|
|
158
|
+
# SessionStore adapters call this inside #append to maintain a summary sidecar
|
|
159
|
+
# incrementally (see SessionStore#list_session_summaries).
|
|
160
|
+
# @param prev [Hash, nil] previous summary entry for this key
|
|
161
|
+
# @param key [Hash] the SessionKey (string keys)
|
|
162
|
+
# @param entries [Array<Hash>] newly appended transcript entries
|
|
163
|
+
# @return [Hash] the updated summary entry
|
|
164
|
+
def self.fold_session_summary(prev, key, entries)
|
|
165
|
+
SessionSummary.fold_session_summary(prev, key, entries)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# List sessions from a SessionStore (store-backed counterpart to list_sessions).
|
|
169
|
+
# @param session_store [SessionStore] the store to read from
|
|
170
|
+
# @return [Array<SDKSessionInfo>] sorted by last_modified descending
|
|
171
|
+
def self.list_sessions_from_store(session_store:, directory: nil, limit: nil, offset: 0)
|
|
172
|
+
Sessions.list_sessions_from_store(session_store: session_store, directory: directory, limit: limit, offset: offset)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Read metadata for a single session from a SessionStore.
|
|
176
|
+
# @return [SDKSessionInfo, nil]
|
|
177
|
+
def self.get_session_info_from_store(session_store:, session_id:, directory: nil)
|
|
178
|
+
Sessions.get_session_info_from_store(session_store: session_store, session_id: session_id, directory: directory)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Read a session's conversation messages from a SessionStore.
|
|
182
|
+
# @return [Array<SessionMessage>]
|
|
183
|
+
def self.get_session_messages_from_store(session_store:, session_id:, directory: nil, limit: nil, offset: 0)
|
|
184
|
+
Sessions.get_session_messages_from_store(session_store: session_store, session_id: session_id,
|
|
185
|
+
directory: directory, limit: limit, offset: offset)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# List subagent IDs for a session from a SessionStore (requires list_subkeys).
|
|
189
|
+
# @return [Array<String>]
|
|
190
|
+
def self.list_subagents_from_store(session_store:, session_id:, directory: nil)
|
|
191
|
+
Sessions.list_subagents_from_store(session_store: session_store, session_id: session_id, directory: directory)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Read a subagent's conversation messages from a SessionStore.
|
|
195
|
+
# @return [Array<SessionMessage>]
|
|
196
|
+
def self.get_subagent_messages_from_store(session_store:, session_id:, agent_id:, directory: nil, limit: nil,
|
|
197
|
+
offset: 0)
|
|
198
|
+
Sessions.get_subagent_messages_from_store(session_store: session_store, session_id: session_id,
|
|
199
|
+
agent_id: agent_id, directory: directory, limit: limit, offset: offset)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Rename a session in a SessionStore (store-backed counterpart to
|
|
203
|
+
# rename_session). Appends a custom-title entry carrying a fresh uuid +
|
|
204
|
+
# timestamp via SessionStore#append.
|
|
205
|
+
# @raise [ArgumentError] if session_id is invalid or title is empty
|
|
206
|
+
def self.rename_session_via_store(session_store:, session_id:, title:, directory: nil)
|
|
207
|
+
SessionMutations.rename_session_via_store(session_store: session_store, session_id: session_id,
|
|
208
|
+
title: title, directory: directory)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Tag a session in a SessionStore (store-backed counterpart to tag_session).
|
|
212
|
+
# Pass nil to clear the tag.
|
|
213
|
+
# @raise [ArgumentError] if session_id is invalid or tag is empty after sanitization
|
|
214
|
+
def self.tag_session_via_store(session_store:, session_id:, tag:, directory: nil)
|
|
215
|
+
SessionMutations.tag_session_via_store(session_store: session_store, session_id: session_id,
|
|
216
|
+
tag: tag, directory: directory)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Delete a session from a SessionStore (store-backed counterpart to
|
|
220
|
+
# delete_session). No-op when the store does not implement #delete
|
|
221
|
+
# (WORM/append-only backends).
|
|
222
|
+
# @raise [ArgumentError] if session_id is invalid
|
|
223
|
+
def self.delete_session_via_store(session_store:, session_id:, directory: nil)
|
|
224
|
+
SessionMutations.delete_session_via_store(session_store: session_store, session_id: session_id,
|
|
225
|
+
directory: directory)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Fork a session in a SessionStore into a new branch with fresh UUIDs
|
|
229
|
+
# (store-backed counterpart to fork_session).
|
|
230
|
+
# @return [ForkSessionResult] result containing the new session ID
|
|
231
|
+
# @raise [ArgumentError] if session_id/up_to_message_id is invalid or there are no messages
|
|
232
|
+
# @raise [Errno::ENOENT] if the source session is not found in the store
|
|
233
|
+
def self.fork_session_via_store(session_store:, session_id:, directory: nil, up_to_message_id: nil, title: nil)
|
|
234
|
+
SessionMutations.fork_session_via_store(session_store: session_store, session_id: session_id,
|
|
235
|
+
directory: directory, up_to_message_id: up_to_message_id, title: title)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Replay a local on-disk session transcript into a SessionStore (migration /
|
|
239
|
+
# gap-backfill). Keys under the on-disk project dir so the imported session is
|
|
240
|
+
# resumable via session_store + resume from the original cwd.
|
|
241
|
+
# @raise [ArgumentError] if session_id is not a valid UUID
|
|
242
|
+
# @raise [Errno::ENOENT] if the session JSONL cannot be found
|
|
243
|
+
def self.import_session_to_store(session_id:, session_store:, directory: nil, include_subagents: true,
|
|
244
|
+
batch_size: TranscriptMirrorBatcher::MAX_PENDING_ENTRIES)
|
|
245
|
+
Sessions.import_session_to_store(session_id: session_id, session_store: session_store, directory: directory,
|
|
246
|
+
include_subagents: include_subagents, batch_size: batch_size)
|
|
247
|
+
end
|
|
248
|
+
|
|
144
249
|
def self.query(prompt:, options: nil, &block)
|
|
145
250
|
return enum_for(:query, prompt: prompt, options: options) unless block
|
|
146
251
|
|
|
@@ -158,15 +263,29 @@ module ClaudeAgentSDK
|
|
|
158
263
|
configured_options = options.dup_with(permission_prompt_tool_name: 'stdio')
|
|
159
264
|
end
|
|
160
265
|
|
|
266
|
+
# Fail fast on invalid session_store combinations before spawning the CLI.
|
|
267
|
+
SessionStores.validate_session_store_options(configured_options)
|
|
268
|
+
|
|
161
269
|
# Resolve callable observers into fresh instances (thread-safe for global defaults)
|
|
162
270
|
resolved_observers = ClaudeAgentSDK.resolve_observers(configured_options.observers)
|
|
163
271
|
|
|
164
272
|
Async do
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
transport = SubprocessCLITransport.new(configured_options)
|
|
273
|
+
materialized = nil
|
|
274
|
+
transport = nil
|
|
275
|
+
query_handler = nil
|
|
169
276
|
begin
|
|
277
|
+
# Resume-from-store: when a session_store is set and resume/continue is
|
|
278
|
+
# requested, load the session into a temp CLAUDE_CONFIG_DIR and repoint
|
|
279
|
+
# options at it (env + --resume) BEFORE spawning. Returns options
|
|
280
|
+
# unchanged when no materialization applies. query() always uses the
|
|
281
|
+
# default subprocess transport, so no custom-transport gate is needed.
|
|
282
|
+
materialized = SessionResume.materialize_resume_session(configured_options)
|
|
283
|
+
configured_options = SessionResume.apply_materialized_options(configured_options, materialized) if materialized
|
|
284
|
+
|
|
285
|
+
# Always use streaming mode with control protocol (matches Python SDK).
|
|
286
|
+
# This sends agents via initialize request instead of CLI args,
|
|
287
|
+
# avoiding OS ARG_MAX limits.
|
|
288
|
+
transport = SubprocessCLITransport.new(configured_options)
|
|
170
289
|
transport.connect
|
|
171
290
|
|
|
172
291
|
# Extract SDK MCP servers
|
|
@@ -207,6 +326,19 @@ module ClaudeAgentSDK
|
|
|
207
326
|
sdk_mcp_servers: sdk_mcp_servers
|
|
208
327
|
)
|
|
209
328
|
|
|
329
|
+
# Mirror transcripts to the session_store, if configured. Installed
|
|
330
|
+
# before #start so the read loop captures transcript_mirror frames.
|
|
331
|
+
if configured_options.session_store
|
|
332
|
+
query_handler.set_transcript_mirror_batcher(
|
|
333
|
+
SessionResume.build_mirror_batcher(
|
|
334
|
+
store: configured_options.session_store,
|
|
335
|
+
env: configured_options.env,
|
|
336
|
+
on_error: ->(key, message) { query_handler.report_mirror_error(key, message) },
|
|
337
|
+
eager: configured_options.session_store_flush.to_s == 'eager'
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
210
342
|
# Start reading messages in background
|
|
211
343
|
query_handler.start
|
|
212
344
|
|
|
@@ -242,11 +374,20 @@ module ClaudeAgentSDK
|
|
|
242
374
|
end
|
|
243
375
|
ensure
|
|
244
376
|
ClaudeAgentSDK.notify_observers(resolved_observers, :on_close)
|
|
245
|
-
# query_handler.close stops the background read task and closes the
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
377
|
+
# query_handler.close stops the background read task and closes the
|
|
378
|
+
# transport (flushing the mirror batcher first). Fall back to a bare
|
|
379
|
+
# transport close when the handler was never built.
|
|
380
|
+
begin
|
|
381
|
+
if query_handler
|
|
382
|
+
query_handler.close
|
|
383
|
+
elsif transport
|
|
384
|
+
transport.close
|
|
385
|
+
end
|
|
386
|
+
ensure
|
|
387
|
+
# Remove the materialized resume temp dir (which holds a redacted
|
|
388
|
+
# .credentials.json copy) AFTER the subprocess has exited, even when
|
|
389
|
+
# close itself raises.
|
|
390
|
+
materialized&.cleanup
|
|
250
391
|
end
|
|
251
392
|
end
|
|
252
393
|
end.wait
|
|
@@ -302,6 +443,7 @@ module ClaudeAgentSDK
|
|
|
302
443
|
@transport = nil
|
|
303
444
|
@query_handler = nil
|
|
304
445
|
@connected = false
|
|
446
|
+
@materialized = nil
|
|
305
447
|
end
|
|
306
448
|
|
|
307
449
|
# Connect to Claude with optional initial prompt.
|
|
@@ -327,55 +469,34 @@ module ClaudeAgentSDK
|
|
|
327
469
|
configured_options = @options.dup_with(permission_prompt_tool_name: 'stdio')
|
|
328
470
|
end
|
|
329
471
|
|
|
330
|
-
#
|
|
331
|
-
|
|
332
|
-
@transport.connect
|
|
472
|
+
# Fail fast on invalid session_store combinations before spawning the CLI.
|
|
473
|
+
SessionStores.validate_session_store_options(configured_options)
|
|
333
474
|
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
# Convert hooks to internal format
|
|
343
|
-
hooks = convert_hooks_to_internal_format(configured_options.hooks) if configured_options.hooks
|
|
344
|
-
|
|
345
|
-
# Extract exclude_dynamic_sections from preset system prompt for the
|
|
346
|
-
# initialize request (older CLIs ignore unknown initialize fields)
|
|
347
|
-
exclude_dynamic_sections = extract_exclude_dynamic_sections(configured_options.system_prompt)
|
|
475
|
+
# Resume-from-store: materialize the session from the store into a temp
|
|
476
|
+
# CLAUDE_CONFIG_DIR BEFORE spawn, then repoint options at it. Skipped for
|
|
477
|
+
# custom transports (the materialized env/--resume only apply to the CLI
|
|
478
|
+
# subprocess). On any later connect failure, disconnect cleans up the dir.
|
|
479
|
+
configured_options = materialize_resume(configured_options)
|
|
348
480
|
|
|
349
|
-
#
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
@connected = true
|
|
368
|
-
|
|
369
|
-
# Optionally send initial prompt/messages after connection is ready.
|
|
370
|
-
case prompt
|
|
371
|
-
when nil
|
|
372
|
-
nil
|
|
373
|
-
when String
|
|
374
|
-
query(prompt)
|
|
375
|
-
else
|
|
376
|
-
prompt.each do |message_json|
|
|
377
|
-
writeln(message_json.to_s)
|
|
481
|
+
# If anything after materialization fails, tear down (closes the
|
|
482
|
+
# subprocess and removes the materialized temp config dir) before
|
|
483
|
+
# surfacing the error, so a partial connect never leaks a temp dir
|
|
484
|
+
# holding a credential copy.
|
|
485
|
+
begin
|
|
486
|
+
connect_inner(configured_options, prompt)
|
|
487
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
488
|
+
# Tear down the partial connect, but never let a cleanup failure (e.g. a
|
|
489
|
+
# custom transport whose #close raises) mask the original connect error.
|
|
490
|
+
# Rescue Exception (not StandardError) so reactor cancellation
|
|
491
|
+
# (Async::Stop < Exception) after materialize_resume set @materialized
|
|
492
|
+
# still runs disconnect -> @materialized.cleanup, never leaking the temp
|
|
493
|
+
# CLAUDE_CONFIG_DIR that holds the redacted .credentials.json copy.
|
|
494
|
+
begin
|
|
495
|
+
disconnect
|
|
496
|
+
rescue StandardError => e
|
|
497
|
+
warn "Claude SDK: cleanup after failed connect raised: #{e.message}"
|
|
378
498
|
end
|
|
499
|
+
raise
|
|
379
500
|
end
|
|
380
501
|
end
|
|
381
502
|
|
|
@@ -513,17 +634,126 @@ module ClaudeAgentSDK
|
|
|
513
634
|
|
|
514
635
|
# Disconnect from Claude
|
|
515
636
|
def disconnect
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
637
|
+
ClaudeAgentSDK.notify_observers(@resolved_observers || [], :on_close) if @connected
|
|
638
|
+
# Tear down whatever exists — robust to a partial/failed connect, where
|
|
639
|
+
# @connected is still false but a transport and/or materialized temp dir
|
|
640
|
+
# were already created. #close on the query handler also closes the
|
|
641
|
+
# transport (flushing the mirror batcher first); the extra @transport
|
|
642
|
+
# close covers a failure before the query handler was built (idempotent).
|
|
643
|
+
#
|
|
644
|
+
# The nested ensures guarantee that even a raising close (e.g. a custom
|
|
645
|
+
# transport whose #close raises) still runs the transport close, resets
|
|
646
|
+
# state, and removes the materialized temp dir (which holds a redacted
|
|
647
|
+
# .credentials.json copy) — so disconnect can never leave the client
|
|
648
|
+
# half-open or leak the temp dir. The original error still propagates.
|
|
649
|
+
begin
|
|
650
|
+
@query_handler&.close
|
|
651
|
+
ensure
|
|
652
|
+
@query_handler = nil
|
|
653
|
+
begin
|
|
654
|
+
@transport&.close
|
|
655
|
+
ensure
|
|
656
|
+
@transport = nil
|
|
657
|
+
@connected = false
|
|
658
|
+
# Remove the materialized resume temp dir AFTER the subprocess exited.
|
|
659
|
+
if @materialized
|
|
660
|
+
@materialized.cleanup
|
|
661
|
+
@materialized = nil
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
end
|
|
523
665
|
end
|
|
524
666
|
|
|
525
667
|
private
|
|
526
668
|
|
|
669
|
+
# Resume-from-store: when a session_store is set (and a subprocess transport
|
|
670
|
+
# is in use), materialize the session into a temp CLAUDE_CONFIG_DIR and
|
|
671
|
+
# return options repointed at it (env + --resume). Returns the options
|
|
672
|
+
# unchanged when no materialization applies. Skipped for non-subprocess
|
|
673
|
+
# transports — the materialized env/--resume only affect the CLI subprocess.
|
|
674
|
+
# Ancestry (<=), not identity: a SubprocessCLITransport subclass spawns the
|
|
675
|
+
# CLI with the same env/--resume semantics, and the transport is constructed
|
|
676
|
+
# AFTER materialization, so the repointed options do reach it.
|
|
677
|
+
def materialize_resume(options)
|
|
678
|
+
subprocess_transport = @transport_class.is_a?(Class) && @transport_class <= SubprocessCLITransport
|
|
679
|
+
return options unless options.session_store && subprocess_transport
|
|
680
|
+
|
|
681
|
+
@materialized = SessionResume.materialize_resume_session(options)
|
|
682
|
+
@materialized ? SessionResume.apply_materialized_options(options, @materialized) : options
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
# The connect body, wrapped by #connect so a failure triggers cleanup.
|
|
686
|
+
def connect_inner(configured_options, prompt)
|
|
687
|
+
# Client always uses streaming mode; keep stdin open for bidirectional communication.
|
|
688
|
+
@transport = @transport_class.new(configured_options, **@transport_args)
|
|
689
|
+
@transport.connect
|
|
690
|
+
|
|
691
|
+
# Extract SDK MCP servers
|
|
692
|
+
sdk_mcp_servers = {}
|
|
693
|
+
if configured_options.mcp_servers.is_a?(Hash)
|
|
694
|
+
configured_options.mcp_servers.each do |name, config|
|
|
695
|
+
sdk_mcp_servers[name] = config[:instance] if config.is_a?(Hash) && config[:type] == 'sdk'
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# Convert hooks to internal format
|
|
700
|
+
hooks = convert_hooks_to_internal_format(configured_options.hooks) if configured_options.hooks
|
|
701
|
+
|
|
702
|
+
# Extract exclude_dynamic_sections from preset system prompt for the
|
|
703
|
+
# initialize request (older CLIs ignore unknown initialize fields)
|
|
704
|
+
exclude_dynamic_sections = extract_exclude_dynamic_sections(configured_options.system_prompt)
|
|
705
|
+
|
|
706
|
+
# Create Query handler
|
|
707
|
+
@query_handler = Query.new(
|
|
708
|
+
transport: @transport,
|
|
709
|
+
is_streaming_mode: true,
|
|
710
|
+
can_use_tool: configured_options.can_use_tool,
|
|
711
|
+
hooks: hooks,
|
|
712
|
+
sdk_mcp_servers: sdk_mcp_servers,
|
|
713
|
+
agents: configured_options.agents,
|
|
714
|
+
exclude_dynamic_sections: exclude_dynamic_sections
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# Mirror transcripts to the session_store, if configured.
|
|
718
|
+
install_transcript_mirror(configured_options)
|
|
719
|
+
|
|
720
|
+
# Start query handler and initialize
|
|
721
|
+
@query_handler.start
|
|
722
|
+
@query_handler.initialize_protocol
|
|
723
|
+
|
|
724
|
+
# Resolve callable observers into fresh instances (thread-safe for global defaults)
|
|
725
|
+
@resolved_observers = ClaudeAgentSDK.resolve_observers(@options.observers)
|
|
726
|
+
|
|
727
|
+
@connected = true
|
|
728
|
+
|
|
729
|
+
# Optionally send initial prompt/messages after connection is ready.
|
|
730
|
+
case prompt
|
|
731
|
+
when nil
|
|
732
|
+
nil
|
|
733
|
+
when String
|
|
734
|
+
query(prompt)
|
|
735
|
+
else
|
|
736
|
+
prompt.each do |message_json|
|
|
737
|
+
writeln(message_json.to_s)
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Build and install the transcript-mirror batcher on the query handler when
|
|
743
|
+
# a session_store is configured, via the shared SessionResume helper (also
|
|
744
|
+
# used by the one-shot query() path).
|
|
745
|
+
def install_transcript_mirror(options)
|
|
746
|
+
return unless options.session_store
|
|
747
|
+
|
|
748
|
+
batcher = SessionResume.build_mirror_batcher(
|
|
749
|
+
store: options.session_store,
|
|
750
|
+
env: options.env,
|
|
751
|
+
on_error: ->(key, message) { @query_handler.report_mirror_error(key, message) },
|
|
752
|
+
eager: options.session_store_flush.to_s == 'eager'
|
|
753
|
+
)
|
|
754
|
+
@query_handler.set_transcript_mirror_batcher(batcher)
|
|
755
|
+
end
|
|
756
|
+
|
|
527
757
|
def convert_hooks_to_internal_format(hooks)
|
|
528
758
|
return nil unless hooks
|
|
529
759
|
|
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: 0.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async
|
|
@@ -86,14 +86,14 @@ dependencies:
|
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version:
|
|
89
|
+
version: 1.87.0
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version:
|
|
96
|
+
version: 1.87.0
|
|
97
97
|
description: Unofficial Ruby SDK for interacting with Claude Code, supporting bidirectional
|
|
98
98
|
conversations, custom tools, and hooks. Not officially maintained by Anthropic.
|
|
99
99
|
email: []
|
|
@@ -125,9 +125,14 @@ files:
|
|
|
125
125
|
- lib/claude_agent_sdk/query.rb
|
|
126
126
|
- lib/claude_agent_sdk/sdk_mcp_server.rb
|
|
127
127
|
- lib/claude_agent_sdk/session_mutations.rb
|
|
128
|
+
- lib/claude_agent_sdk/session_resume.rb
|
|
129
|
+
- lib/claude_agent_sdk/session_store.rb
|
|
130
|
+
- lib/claude_agent_sdk/session_summary.rb
|
|
128
131
|
- lib/claude_agent_sdk/sessions.rb
|
|
129
132
|
- lib/claude_agent_sdk/streaming.rb
|
|
130
133
|
- lib/claude_agent_sdk/subprocess_cli_transport.rb
|
|
134
|
+
- lib/claude_agent_sdk/testing/session_store_conformance.rb
|
|
135
|
+
- lib/claude_agent_sdk/transcript_mirror_batcher.rb
|
|
131
136
|
- lib/claude_agent_sdk/transport.rb
|
|
132
137
|
- lib/claude_agent_sdk/types.rb
|
|
133
138
|
- lib/claude_agent_sdk/version.rb
|