claude-agent-sdk 0.35.0 → 0.36.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 +57 -0
- data/README.md +16 -7
- data/docs/cli-installer.md +16 -2
- data/docs/client.md +18 -3
- data/docs/errors.md +15 -1
- data/docs/hooks-and-permissions.md +22 -0
- data/docs/mcp-servers.md +37 -7
- data/docs/rails.md +3 -4
- data/docs/sessions.md +69 -33
- data/lib/claude_agent_sdk/cancellation_signal.rb +2 -1
- data/lib/claude_agent_sdk/cli_installer.rb +38 -8
- data/lib/claude_agent_sdk/deprecation.rb +51 -0
- data/lib/claude_agent_sdk/errors.rb +8 -0
- data/lib/claude_agent_sdk/fiber_boundary.rb +111 -2
- data/lib/claude_agent_sdk/option_warnings.rb +0 -2
- data/lib/claude_agent_sdk/query.rb +49 -8
- data/lib/claude_agent_sdk/railtie.rb +14 -3
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +36 -25
- data/lib/claude_agent_sdk/session_mutations.rb +10 -10
- data/lib/claude_agent_sdk/session_resume.rb +19 -24
- data/lib/claude_agent_sdk/session_store.rb +28 -18
- data/lib/claude_agent_sdk/session_summary.rb +8 -3
- data/lib/claude_agent_sdk/sessions.rb +104 -18
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +4 -13
- data/lib/claude_agent_sdk/tasks/claude_agent_sdk.rake +12 -5
- data/lib/claude_agent_sdk/testing/session_store_conformance.rb +1 -1
- data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +17 -1
- data/lib/claude_agent_sdk/types.rb +2 -2
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +257 -56
- data/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt +3 -3
- metadata +2 -1
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -20,6 +20,7 @@ require_relative 'claude_agent_sdk/session_resume'
|
|
|
20
20
|
require_relative 'claude_agent_sdk/session_mutations'
|
|
21
21
|
require_relative 'claude_agent_sdk/fiber_boundary'
|
|
22
22
|
require_relative 'claude_agent_sdk/option_warnings'
|
|
23
|
+
require_relative 'claude_agent_sdk/deprecation'
|
|
23
24
|
# Rails apps only: Bundler.require runs after `require 'rails'`, so the
|
|
24
25
|
# Railtie (rake tasks; the generator lives under lib/generators) is picked up
|
|
25
26
|
# there and nowhere else.
|
|
@@ -40,6 +41,7 @@ module ClaudeAgentSDK
|
|
|
40
41
|
# skipped — most commonly a Class passed instead of an instance, which
|
|
41
42
|
# previously produced silent zero instrumentation (every notify raised
|
|
42
43
|
# NoMethodError, swallowed by notify_observers' error containment).
|
|
44
|
+
# @api private
|
|
43
45
|
def self.resolve_observers(observers)
|
|
44
46
|
Array(observers).filter_map do |obs|
|
|
45
47
|
resolved = obs.respond_to?(:call) ? obs.call : obs
|
|
@@ -60,6 +62,7 @@ module ClaudeAgentSDK
|
|
|
60
62
|
# configs may use String or Symbol keys (and a Symbol :sdk type) — the
|
|
61
63
|
# recognition rule must match CommandBuilder#append_mcp_servers, which
|
|
62
64
|
# strips the instance from exactly these entries.
|
|
65
|
+
# @api private
|
|
63
66
|
def self.extract_sdk_mcp_servers(mcp_servers)
|
|
64
67
|
return {} unless mcp_servers.is_a?(Hash)
|
|
65
68
|
|
|
@@ -75,6 +78,7 @@ module ClaudeAgentSDK
|
|
|
75
78
|
|
|
76
79
|
# Internal: normalize hook lists for the control protocol. An absent or
|
|
77
80
|
# disabled event must not become an empty registration in initialize.
|
|
81
|
+
# @api private
|
|
78
82
|
def self.convert_hooks_to_internal_format(hooks)
|
|
79
83
|
return nil unless hooks
|
|
80
84
|
|
|
@@ -108,6 +112,7 @@ module ClaudeAgentSDK
|
|
|
108
112
|
# guarantees for can_use_tool — the permission round-trip works. The old
|
|
109
113
|
# "requires streaming mode" ArgumentError was a needless restriction
|
|
110
114
|
# (Python #1204).
|
|
115
|
+
# @api private
|
|
111
116
|
def self.configure_can_use_tool(options)
|
|
112
117
|
return options unless options.can_use_tool
|
|
113
118
|
|
|
@@ -124,6 +129,7 @@ module ClaudeAgentSDK
|
|
|
124
129
|
# Internal: pull exclude_dynamic_sections out of a preset system prompt for
|
|
125
130
|
# the initialize request (older CLIs ignore unknown initialize fields).
|
|
126
131
|
# Shared by Client#connect and the one-shot query() path.
|
|
132
|
+
# @api private
|
|
127
133
|
def self.extract_exclude_dynamic_sections(system_prompt)
|
|
128
134
|
if system_prompt.is_a?(SystemPromptPreset)
|
|
129
135
|
eds = system_prompt.exclude_dynamic_sections
|
|
@@ -143,6 +149,7 @@ module ClaudeAgentSDK
|
|
|
143
149
|
# String or file prompt has no snapshot, and only a genuine true/false is
|
|
144
150
|
# forwarded — `snapshot: false` is the primary use case, so the Hash lookup
|
|
145
151
|
# must not collapse it to nil. Shared by Client#connect and query().
|
|
152
|
+
# @api private
|
|
146
153
|
def self.extract_system_prompt_snapshot(system_prompt)
|
|
147
154
|
case system_prompt
|
|
148
155
|
when SystemPromptPreset, SystemPromptCustom
|
|
@@ -162,6 +169,7 @@ module ClaudeAgentSDK
|
|
|
162
169
|
# Each observer is invoked through FiberBoundary so that user code runs
|
|
163
170
|
# on a plain thread (no Fiber scheduler) even when called from inside
|
|
164
171
|
# the SDK's Async reactor — or in place when scheduling is :inline.
|
|
172
|
+
# @api private
|
|
165
173
|
def self.notify_observers(observers, method, *args, scheduling: :thread, wrapper: nil)
|
|
166
174
|
observers.each do |obs|
|
|
167
175
|
FiberBoundary.invoke(scheduling: scheduling, wrapper: wrapper) { obs.send(method, *args) }
|
|
@@ -188,8 +196,8 @@ module ClaudeAgentSDK
|
|
|
188
196
|
#
|
|
189
197
|
# @example Inside an inline-mode tool handler
|
|
190
198
|
# ClaudeAgentSDK.offload { blocking_db_call }
|
|
191
|
-
def self.offload(&
|
|
192
|
-
FiberBoundary.invoke(&
|
|
199
|
+
def self.offload(&)
|
|
200
|
+
FiberBoundary.invoke(&)
|
|
193
201
|
end
|
|
194
202
|
|
|
195
203
|
# Guards the once-per-process flag below: two sessions connecting
|
|
@@ -202,6 +210,7 @@ module ClaudeAgentSDK
|
|
|
202
210
|
# almost certainly violates inline mode's fiber-isolation precondition
|
|
203
211
|
# (solid_queue fiber workers require isolation_level = :fiber).
|
|
204
212
|
# defined? probing only; the SDK never loads ActiveSupport itself.
|
|
213
|
+
# @api private
|
|
205
214
|
def self.check_inline_isolation(scheduling)
|
|
206
215
|
return unless scheduling == :inline
|
|
207
216
|
return unless defined?(ActiveSupport::IsolatedExecutionState)
|
|
@@ -224,6 +233,7 @@ module ClaudeAgentSDK
|
|
|
224
233
|
# when there is none (non-user messages, tool_result-only content, …).
|
|
225
234
|
# Only Hash and JSON-string items are inspected; arbitrary objects written
|
|
226
235
|
# via to_s are never notified.
|
|
236
|
+
# @api private
|
|
227
237
|
def self.extract_user_prompt_text(message)
|
|
228
238
|
data = case message
|
|
229
239
|
when Hash then message
|
|
@@ -254,6 +264,7 @@ module ClaudeAgentSDK
|
|
|
254
264
|
# when there is no extractable text — on_user_prompt('') would latch
|
|
255
265
|
# OTelObserver's first-prompt buffer while never setting the attribute,
|
|
256
266
|
# permanently suppressing later real prompts.
|
|
267
|
+
# @api private
|
|
257
268
|
def self.prompt_text_from_content(content)
|
|
258
269
|
case content
|
|
259
270
|
when String
|
|
@@ -273,6 +284,7 @@ module ClaudeAgentSDK
|
|
|
273
284
|
# Wrap a streaming-input enumerable so observers get on_user_prompt for
|
|
274
285
|
# each user message before it is written to stdin. Identity when no
|
|
275
286
|
# observers are configured.
|
|
287
|
+
# @api private
|
|
276
288
|
def self.observing_prompt_stream(prompt, observers, scheduling: :thread, wrapper: nil)
|
|
277
289
|
return prompt if observers.empty?
|
|
278
290
|
|
|
@@ -287,6 +299,7 @@ module ClaudeAgentSDK
|
|
|
287
299
|
|
|
288
300
|
# Look up a value in a hash that may use symbol or string keys in camelCase or snake_case.
|
|
289
301
|
# Returns the first non-nil value found, preserving false as a meaningful value.
|
|
302
|
+
# @api private
|
|
290
303
|
def self.flexible_fetch(hash, camel_key, snake_key)
|
|
291
304
|
val = hash[camel_key.to_sym]
|
|
292
305
|
val = hash[camel_key.to_s] if val.nil?
|
|
@@ -295,93 +308,211 @@ module ClaudeAgentSDK
|
|
|
295
308
|
val
|
|
296
309
|
end
|
|
297
310
|
|
|
298
|
-
#
|
|
299
|
-
#
|
|
311
|
+
# ---- Session browsing & mutation ----
|
|
312
|
+
#
|
|
313
|
+
# One function per operation. By default each reads or writes the local-disk
|
|
314
|
+
# transcripts under CLAUDE_CONFIG_DIR; pass +session_store:+ to operate on a
|
|
315
|
+
# SessionStore instead. The two paths differ in a few documented ways:
|
|
316
|
+
#
|
|
317
|
+
# - +directory: nil+ searches every project directory on disk, but means the
|
|
318
|
+
# current working directory with a store (a SessionStore is keyed by
|
|
319
|
+
# project_key and cannot enumerate projects — parity with the Python SDK).
|
|
320
|
+
# - +include_worktrees:+ filters only on disk (list_sessions); with a
|
|
321
|
+
# +session_store:+, anything but the default +true+ raises ArgumentError.
|
|
322
|
+
|
|
323
|
+
# List sessions for a directory (or all sessions), newest first.
|
|
324
|
+
# @param directory [String, nil] Working directory to list sessions for. On
|
|
325
|
+
# disk, nil lists every project; with a session_store, nil means the
|
|
326
|
+
# current working directory.
|
|
300
327
|
# @param limit [Integer, nil] Maximum number of sessions to return
|
|
301
328
|
# @param offset [Integer] Number of sessions to skip (for pagination)
|
|
302
|
-
# @param include_worktrees [Boolean]
|
|
329
|
+
# @param include_worktrees [Boolean] Disk only: also list the project's git
|
|
330
|
+
# worktree sessions. A store has no worktrees, so with a session_store only
|
|
331
|
+
# the default true is accepted; false or nil raises ArgumentError (the
|
|
332
|
+
# store path cannot apply the filter the caller asked for).
|
|
333
|
+
# @param session_store [SessionStore, nil] List from this store instead of
|
|
334
|
+
# local disk. Uses the store's list_session_summaries when implemented,
|
|
335
|
+
# else list_sessions + one load per listed session.
|
|
303
336
|
# @return [Array<SDKSessionInfo>] Sessions sorted by last_modified descending
|
|
304
|
-
|
|
337
|
+
# @raise [ArgumentError] if include_worktrees is not true with a session_store,
|
|
338
|
+
# or the store implements neither list_session_summaries nor list_sessions
|
|
339
|
+
def self.list_sessions(directory: nil, limit: nil, offset: 0, include_worktrees: true, session_store: nil)
|
|
340
|
+
unless session_store.nil?
|
|
341
|
+
unless include_worktrees == true
|
|
342
|
+
raise ArgumentError, "include_worktrees: #{include_worktrees.inspect} applies only to local-disk " \
|
|
343
|
+
'listing; a session_store is keyed by project and has no worktrees to exclude'
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
return Sessions.list_sessions_from_store(session_store: session_store, directory: directory,
|
|
347
|
+
limit: limit, offset: offset)
|
|
348
|
+
end
|
|
349
|
+
|
|
305
350
|
Sessions.list_sessions(directory: directory, limit: limit, offset: offset, include_worktrees: include_worktrees)
|
|
306
351
|
end
|
|
307
352
|
|
|
308
353
|
# Read metadata for a single session by ID (no full directory scan)
|
|
309
354
|
# @param session_id [String] UUID of the session to look up
|
|
310
|
-
# @param directory [String, nil] Project directory path
|
|
311
|
-
#
|
|
312
|
-
|
|
355
|
+
# @param directory [String, nil] Project directory path. On disk, nil
|
|
356
|
+
# searches every project; with a session_store, nil means the current
|
|
357
|
+
# working directory.
|
|
358
|
+
# @param session_store [SessionStore, nil] Read from this store instead of local disk
|
|
359
|
+
# @return [SDKSessionInfo, nil] Session info, or nil if not found / sidechain / no summary
|
|
360
|
+
def self.get_session_info(session_id:, directory: nil, session_store: nil)
|
|
361
|
+
unless session_store.nil?
|
|
362
|
+
return Sessions.get_session_info_from_store(session_store: session_store, session_id: session_id,
|
|
363
|
+
directory: directory)
|
|
364
|
+
end
|
|
365
|
+
|
|
313
366
|
Sessions.get_session_info(session_id: session_id, directory: directory)
|
|
314
367
|
end
|
|
315
368
|
|
|
316
369
|
# Get messages from a session transcript
|
|
317
370
|
# @param session_id [String] The session UUID
|
|
318
|
-
# @param directory [String, nil] Working directory to search in
|
|
371
|
+
# @param directory [String, nil] Working directory to search in. On disk,
|
|
372
|
+
# nil searches every project; with a session_store, nil means the current
|
|
373
|
+
# working directory.
|
|
319
374
|
# @param limit [Integer, nil] Maximum number of messages
|
|
320
375
|
# @param offset [Integer] Number of messages to skip
|
|
376
|
+
# @param session_store [SessionStore, nil] Read from this store instead of local disk
|
|
321
377
|
# @return [Array<SessionMessage>] Ordered messages from the session
|
|
322
|
-
def self.get_session_messages(session_id:, directory: nil, limit: nil, offset: 0)
|
|
378
|
+
def self.get_session_messages(session_id:, directory: nil, limit: nil, offset: 0, session_store: nil)
|
|
379
|
+
unless session_store.nil?
|
|
380
|
+
return Sessions.get_session_messages_from_store(session_store: session_store, session_id: session_id,
|
|
381
|
+
directory: directory, limit: limit, offset: offset)
|
|
382
|
+
end
|
|
383
|
+
|
|
323
384
|
Sessions.get_session_messages(session_id: session_id, directory: directory, limit: limit, offset: offset)
|
|
324
385
|
end
|
|
325
386
|
|
|
326
|
-
# List subagent IDs recorded for a session
|
|
387
|
+
# List subagent IDs recorded for a session
|
|
327
388
|
# @param session_id [String] The session UUID
|
|
328
|
-
# @param directory [String, nil] Working directory to search in
|
|
389
|
+
# @param directory [String, nil] Working directory to search in. On disk,
|
|
390
|
+
# nil searches every project; with a session_store, nil means the current
|
|
391
|
+
# working directory.
|
|
392
|
+
# @param session_store [SessionStore, nil] Read from this store instead of
|
|
393
|
+
# local disk (the store must implement list_subkeys)
|
|
329
394
|
# @return [Array<String>] Subagent IDs
|
|
330
|
-
|
|
395
|
+
# @raise [ArgumentError] if the session_store does not implement list_subkeys
|
|
396
|
+
def self.list_subagents(session_id:, directory: nil, session_store: nil)
|
|
397
|
+
unless session_store.nil?
|
|
398
|
+
return Sessions.list_subagents_from_store(session_store: session_store, session_id: session_id,
|
|
399
|
+
directory: directory)
|
|
400
|
+
end
|
|
401
|
+
|
|
331
402
|
Sessions.list_subagents(session_id: session_id, directory: directory)
|
|
332
403
|
end
|
|
333
404
|
|
|
334
|
-
# Read a subagent's optional metadata (not live status)
|
|
405
|
+
# Read a subagent's optional metadata (not live status). With a
|
|
406
|
+
# session_store, the last agent_metadata entry wins and its synthetic type
|
|
407
|
+
# marker is omitted.
|
|
335
408
|
# @param session_id [String] The parent session UUID
|
|
336
409
|
# @param agent_id [String] The subagent ID, without the agent- prefix
|
|
337
|
-
# @param directory [String, nil] Project directory to search in
|
|
410
|
+
# @param directory [String, nil] Project directory to search in. On disk,
|
|
411
|
+
# nil searches every project; with a session_store, nil means the current
|
|
412
|
+
# working directory.
|
|
413
|
+
# @param session_store [SessionStore, nil] Read from this store instead of local disk
|
|
338
414
|
# @return [Hash{String => Object}, nil] CLI metadata, or nil if unavailable
|
|
339
|
-
def self.get_subagent_metadata(session_id:, agent_id:, directory: nil)
|
|
415
|
+
def self.get_subagent_metadata(session_id:, agent_id:, directory: nil, session_store: nil)
|
|
416
|
+
unless session_store.nil?
|
|
417
|
+
return Sessions.get_subagent_metadata_from_store(session_store: session_store, session_id: session_id,
|
|
418
|
+
agent_id: agent_id, directory: directory)
|
|
419
|
+
end
|
|
420
|
+
|
|
340
421
|
Sessions.get_subagent_metadata(session_id: session_id, agent_id: agent_id, directory: directory)
|
|
341
422
|
end
|
|
342
423
|
|
|
343
|
-
# Read a subagent's conversation messages
|
|
424
|
+
# Read a subagent's conversation messages
|
|
344
425
|
# @param session_id [String] The session UUID
|
|
345
426
|
# @param agent_id [String] The subagent ID (without the agent- prefix)
|
|
346
|
-
# @param directory [String, nil] Working directory to search in
|
|
427
|
+
# @param directory [String, nil] Working directory to search in. On disk,
|
|
428
|
+
# nil searches every project; with a session_store, nil means the current
|
|
429
|
+
# working directory.
|
|
347
430
|
# @param limit [Integer, nil] Maximum number of messages
|
|
348
431
|
# @param offset [Integer] Number of messages to skip
|
|
432
|
+
# @param session_store [SessionStore, nil] Read from this store instead of local disk
|
|
349
433
|
# @return [Array<SessionMessage>] Ordered messages from the subagent
|
|
350
|
-
def self.get_subagent_messages(session_id:, agent_id:, directory: nil, limit: nil, offset: 0)
|
|
434
|
+
def self.get_subagent_messages(session_id:, agent_id:, directory: nil, limit: nil, offset: 0, session_store: nil)
|
|
435
|
+
unless session_store.nil?
|
|
436
|
+
return Sessions.get_subagent_messages_from_store(session_store: session_store, session_id: session_id,
|
|
437
|
+
agent_id: agent_id, directory: directory,
|
|
438
|
+
limit: limit, offset: offset)
|
|
439
|
+
end
|
|
440
|
+
|
|
351
441
|
Sessions.get_subagent_messages(session_id: session_id, agent_id: agent_id,
|
|
352
442
|
directory: directory, limit: limit, offset: offset)
|
|
353
443
|
end
|
|
354
444
|
|
|
355
|
-
# Rename a session by appending a custom-title entry
|
|
445
|
+
# Rename a session by appending a custom-title entry. With a session_store
|
|
446
|
+
# the entry is appended via SessionStore#append and carries a fresh uuid +
|
|
447
|
+
# timestamp (so uuid-deduping adapters treat it correctly).
|
|
356
448
|
# @param session_id [String] UUID of the session to rename
|
|
357
449
|
# @param title [String] New session title
|
|
358
|
-
# @param directory [String, nil] Project directory path
|
|
359
|
-
|
|
450
|
+
# @param directory [String, nil] Project directory path (nil = cwd with a session_store)
|
|
451
|
+
# @param session_store [SessionStore, nil] Rename in this store instead of on local disk
|
|
452
|
+
# @raise [ArgumentError] if session_id is invalid or title is empty
|
|
453
|
+
# @raise [Errno::ENOENT] if the session is not found (nothing is written)
|
|
454
|
+
def self.rename_session(session_id:, title:, directory: nil, session_store: nil)
|
|
455
|
+
unless session_store.nil?
|
|
456
|
+
return SessionMutations.rename_session_via_store(session_store: session_store, session_id: session_id,
|
|
457
|
+
title: title, directory: directory)
|
|
458
|
+
end
|
|
459
|
+
|
|
360
460
|
SessionMutations.rename_session(session_id: session_id, title: title, directory: directory)
|
|
361
461
|
end
|
|
362
462
|
|
|
363
463
|
# Tag a session. Pass nil to clear the tag.
|
|
364
464
|
# @param session_id [String] UUID of the session to tag
|
|
365
465
|
# @param tag [String, nil] Tag string, or nil to clear
|
|
366
|
-
# @param directory [String, nil] Project directory path
|
|
367
|
-
|
|
466
|
+
# @param directory [String, nil] Project directory path (nil = cwd with a session_store)
|
|
467
|
+
# @param session_store [SessionStore, nil] Tag in this store instead of on local disk
|
|
468
|
+
# @raise [ArgumentError] if session_id is invalid or tag is empty after sanitization
|
|
469
|
+
# @raise [Errno::ENOENT] if the session is not found (nothing is written)
|
|
470
|
+
def self.tag_session(session_id:, tag:, directory: nil, session_store: nil)
|
|
471
|
+
unless session_store.nil?
|
|
472
|
+
return SessionMutations.tag_session_via_store(session_store: session_store, session_id: session_id,
|
|
473
|
+
tag: tag, directory: directory)
|
|
474
|
+
end
|
|
475
|
+
|
|
368
476
|
SessionMutations.tag_session(session_id: session_id, tag: tag, directory: directory)
|
|
369
477
|
end
|
|
370
478
|
|
|
371
|
-
# Delete a session
|
|
479
|
+
# Delete a session (hard delete). On disk, removes its JSONL file and
|
|
480
|
+
# subagent directory, raising Errno::ENOENT if the session is not found.
|
|
481
|
+
# With a session_store, calls SessionStore#delete — a no-op when the store
|
|
482
|
+
# does not implement #delete (WORM/append-only backends); whether subagent
|
|
483
|
+
# subkeys are removed too depends on the store's cascade semantics.
|
|
372
484
|
# @param session_id [String] UUID of the session to delete
|
|
373
|
-
# @param directory [String, nil] Project directory path
|
|
374
|
-
|
|
485
|
+
# @param directory [String, nil] Project directory path (nil = cwd with a session_store)
|
|
486
|
+
# @param session_store [SessionStore, nil] Delete from this store instead of local disk
|
|
487
|
+
# @raise [ArgumentError] if session_id is invalid
|
|
488
|
+
# @raise [Errno::ENOENT] on disk, if the session file cannot be found
|
|
489
|
+
def self.delete_session(session_id:, directory: nil, session_store: nil)
|
|
490
|
+
unless session_store.nil?
|
|
491
|
+
return SessionMutations.delete_session_via_store(session_store: session_store, session_id: session_id,
|
|
492
|
+
directory: directory)
|
|
493
|
+
end
|
|
494
|
+
|
|
375
495
|
SessionMutations.delete_session(session_id: session_id, directory: directory)
|
|
376
496
|
end
|
|
377
497
|
|
|
378
|
-
# Fork a session into a new branch with fresh UUIDs.
|
|
498
|
+
# Fork a session into a new branch with fresh UUIDs. With a session_store
|
|
499
|
+
# the fork transform runs over the store's entries and the fork is appended
|
|
500
|
+
# to the same store.
|
|
379
501
|
# @param session_id [String] UUID of the session to fork
|
|
380
|
-
# @param directory [String, nil] Project directory path
|
|
502
|
+
# @param directory [String, nil] Project directory path (nil = cwd with a session_store)
|
|
381
503
|
# @param up_to_message_id [String, nil] Truncate the fork at this message UUID
|
|
382
504
|
# @param title [String, nil] Custom title for the fork
|
|
505
|
+
# @param session_store [SessionStore, nil] Fork within this store instead of on local disk
|
|
383
506
|
# @return [ForkSessionResult] Result containing the new session ID
|
|
384
|
-
|
|
507
|
+
# @raise [ArgumentError] if session_id/up_to_message_id is invalid or there are no messages
|
|
508
|
+
# @raise [Errno::ENOENT] if the source session is not found
|
|
509
|
+
def self.fork_session(session_id:, directory: nil, up_to_message_id: nil, title: nil, session_store: nil)
|
|
510
|
+
unless session_store.nil?
|
|
511
|
+
return SessionMutations.fork_session_via_store(session_store: session_store, session_id: session_id,
|
|
512
|
+
directory: directory, up_to_message_id: up_to_message_id,
|
|
513
|
+
title: title)
|
|
514
|
+
end
|
|
515
|
+
|
|
385
516
|
SessionMutations.fork_session(session_id: session_id, directory: directory,
|
|
386
517
|
up_to_message_id: up_to_message_id, title: title)
|
|
387
518
|
end
|
|
@@ -406,81 +537,83 @@ module ClaudeAgentSDK
|
|
|
406
537
|
SessionSummary.fold_session_summary(prev, key, entries)
|
|
407
538
|
end
|
|
408
539
|
|
|
409
|
-
#
|
|
410
|
-
#
|
|
540
|
+
# ---- Deprecated store twins (removed in 1.0) ----
|
|
541
|
+
#
|
|
542
|
+
# Each forwards to the same implementation as before — not to the merged
|
|
543
|
+
# function, so a nil session_store keeps failing as it always did instead
|
|
544
|
+
# of silently reading local disk — after one warning per method per process.
|
|
545
|
+
|
|
546
|
+
# @deprecated Use {.list_sessions} with +session_store:+. Removed in 1.0.
|
|
411
547
|
# @return [Array<SDKSessionInfo>] sorted by last_modified descending
|
|
412
548
|
def self.list_sessions_from_store(session_store:, directory: nil, limit: nil, offset: 0)
|
|
549
|
+
Deprecation.warn_once(:list_sessions_from_store, 'list_sessions(session_store: store)')
|
|
413
550
|
Sessions.list_sessions_from_store(session_store: session_store, directory: directory, limit: limit, offset: offset)
|
|
414
551
|
end
|
|
415
552
|
|
|
416
|
-
#
|
|
553
|
+
# @deprecated Use {.get_session_info} with +session_store:+. Removed in 1.0.
|
|
417
554
|
# @return [SDKSessionInfo, nil]
|
|
418
555
|
def self.get_session_info_from_store(session_store:, session_id:, directory: nil)
|
|
556
|
+
Deprecation.warn_once(:get_session_info_from_store, 'get_session_info(session_store: store, ...)')
|
|
419
557
|
Sessions.get_session_info_from_store(session_store: session_store, session_id: session_id, directory: directory)
|
|
420
558
|
end
|
|
421
559
|
|
|
422
|
-
#
|
|
560
|
+
# @deprecated Use {.get_session_messages} with +session_store:+. Removed in 1.0.
|
|
423
561
|
# @return [Array<SessionMessage>]
|
|
424
562
|
def self.get_session_messages_from_store(session_store:, session_id:, directory: nil, limit: nil, offset: 0)
|
|
563
|
+
Deprecation.warn_once(:get_session_messages_from_store, 'get_session_messages(session_store: store, ...)')
|
|
425
564
|
Sessions.get_session_messages_from_store(session_store: session_store, session_id: session_id,
|
|
426
565
|
directory: directory, limit: limit, offset: offset)
|
|
427
566
|
end
|
|
428
567
|
|
|
429
|
-
#
|
|
568
|
+
# @deprecated Use {.list_subagents} with +session_store:+. Removed in 1.0.
|
|
430
569
|
# @return [Array<String>]
|
|
431
570
|
def self.list_subagents_from_store(session_store:, session_id:, directory: nil)
|
|
571
|
+
Deprecation.warn_once(:list_subagents_from_store, 'list_subagents(session_store: store, ...)')
|
|
432
572
|
Sessions.list_subagents_from_store(session_store: session_store, session_id: session_id, directory: directory)
|
|
433
573
|
end
|
|
434
574
|
|
|
435
|
-
#
|
|
575
|
+
# @deprecated Use {.get_subagent_metadata} with +session_store:+. Removed in 1.0.
|
|
436
576
|
# @return [Hash{String => Object}, nil]
|
|
437
577
|
def self.get_subagent_metadata_from_store(session_store:, session_id:, agent_id:, directory: nil)
|
|
578
|
+
Deprecation.warn_once(:get_subagent_metadata_from_store, 'get_subagent_metadata(session_store: store, ...)')
|
|
438
579
|
Sessions.get_subagent_metadata_from_store(session_store: session_store, session_id: session_id,
|
|
439
580
|
agent_id: agent_id, directory: directory)
|
|
440
581
|
end
|
|
441
582
|
|
|
442
|
-
#
|
|
583
|
+
# @deprecated Use {.get_subagent_messages} with +session_store:+. Removed in 1.0.
|
|
443
584
|
# @return [Array<SessionMessage>]
|
|
444
585
|
def self.get_subagent_messages_from_store(session_store:, session_id:, agent_id:, directory: nil, limit: nil,
|
|
445
586
|
offset: 0)
|
|
587
|
+
Deprecation.warn_once(:get_subagent_messages_from_store, 'get_subagent_messages(session_store: store, ...)')
|
|
446
588
|
Sessions.get_subagent_messages_from_store(session_store: session_store, session_id: session_id,
|
|
447
589
|
agent_id: agent_id, directory: directory, limit: limit, offset: offset)
|
|
448
590
|
end
|
|
449
591
|
|
|
450
|
-
#
|
|
451
|
-
# rename_session). Appends a custom-title entry carrying a fresh uuid +
|
|
452
|
-
# timestamp via SessionStore#append.
|
|
453
|
-
# @raise [ArgumentError] if session_id is invalid or title is empty
|
|
454
|
-
# @raise [Errno::ENOENT] if the session is not found in the store (nothing is appended)
|
|
592
|
+
# @deprecated Use {.rename_session} with +session_store:+. Removed in 1.0.
|
|
455
593
|
def self.rename_session_via_store(session_store:, session_id:, title:, directory: nil)
|
|
594
|
+
Deprecation.warn_once(:rename_session_via_store, 'rename_session(session_store: store, ...)')
|
|
456
595
|
SessionMutations.rename_session_via_store(session_store: session_store, session_id: session_id,
|
|
457
596
|
title: title, directory: directory)
|
|
458
597
|
end
|
|
459
598
|
|
|
460
|
-
#
|
|
461
|
-
# Pass nil to clear the tag.
|
|
462
|
-
# @raise [ArgumentError] if session_id is invalid or tag is empty after sanitization
|
|
463
|
-
# @raise [Errno::ENOENT] if the session is not found in the store (nothing is appended)
|
|
599
|
+
# @deprecated Use {.tag_session} with +session_store:+. Removed in 1.0.
|
|
464
600
|
def self.tag_session_via_store(session_store:, session_id:, tag:, directory: nil)
|
|
601
|
+
Deprecation.warn_once(:tag_session_via_store, 'tag_session(session_store: store, ...)')
|
|
465
602
|
SessionMutations.tag_session_via_store(session_store: session_store, session_id: session_id,
|
|
466
603
|
tag: tag, directory: directory)
|
|
467
604
|
end
|
|
468
605
|
|
|
469
|
-
#
|
|
470
|
-
# delete_session). No-op when the store does not implement #delete
|
|
471
|
-
# (WORM/append-only backends).
|
|
472
|
-
# @raise [ArgumentError] if session_id is invalid
|
|
606
|
+
# @deprecated Use {.delete_session} with +session_store:+. Removed in 1.0.
|
|
473
607
|
def self.delete_session_via_store(session_store:, session_id:, directory: nil)
|
|
608
|
+
Deprecation.warn_once(:delete_session_via_store, 'delete_session(session_store: store, ...)')
|
|
474
609
|
SessionMutations.delete_session_via_store(session_store: session_store, session_id: session_id,
|
|
475
610
|
directory: directory)
|
|
476
611
|
end
|
|
477
612
|
|
|
478
|
-
#
|
|
479
|
-
#
|
|
480
|
-
# @return [ForkSessionResult] result containing the new session ID
|
|
481
|
-
# @raise [ArgumentError] if session_id/up_to_message_id is invalid or there are no messages
|
|
482
|
-
# @raise [Errno::ENOENT] if the source session is not found in the store
|
|
613
|
+
# @deprecated Use {.fork_session} with +session_store:+. Removed in 1.0.
|
|
614
|
+
# @return [ForkSessionResult]
|
|
483
615
|
def self.fork_session_via_store(session_store:, session_id:, directory: nil, up_to_message_id: nil, title: nil)
|
|
616
|
+
Deprecation.warn_once(:fork_session_via_store, 'fork_session(session_store: store, ...)')
|
|
484
617
|
SessionMutations.fork_session_via_store(session_store: session_store, session_id: session_id,
|
|
485
618
|
directory: directory, up_to_message_id: up_to_message_id, title: title)
|
|
486
619
|
end
|
|
@@ -702,6 +835,51 @@ module ClaudeAgentSDK
|
|
|
702
835
|
end).wait
|
|
703
836
|
end
|
|
704
837
|
|
|
838
|
+
# Run a query to completion and return its final ResultMessage.
|
|
839
|
+
#
|
|
840
|
+
# The one-call form of {.query} for when you want the answer rather than
|
|
841
|
+
# the stream: +ask(prompt).result+ is the final text, and the returned
|
|
842
|
+
# ResultMessage also carries cost, usage, duration, session_id and
|
|
843
|
+
# structured_output. It is {.query} underneath — same prompt types, same
|
|
844
|
+
# options, same errors — and it consumes the whole stream before
|
|
845
|
+
# returning. With an Enumerable prompt that produces several turns, the
|
|
846
|
+
# last ResultMessage is returned.
|
|
847
|
+
#
|
|
848
|
+
# An error result is returned like any other (check #is_error / #subtype);
|
|
849
|
+
# when the CLI then exits non-zero, {.query} raises ResultError, which
|
|
850
|
+
# propagates from here unchanged.
|
|
851
|
+
#
|
|
852
|
+
# @param prompt [String, Enumerable] The prompt, as for {.query}
|
|
853
|
+
# @param options [ClaudeAgentOptions, nil] Optional configuration
|
|
854
|
+
# @param transport [Transport, nil] Optional transport, as for {.query}
|
|
855
|
+
# @yield [Message] Optionally, every message as it arrives (including the
|
|
856
|
+
# final ResultMessage), so you can stream progress and still get the
|
|
857
|
+
# result back. Runs where {.query}'s block runs. The block observes the
|
|
858
|
+
# stream; it cannot end it early — use {.query} for that.
|
|
859
|
+
# @return [ResultMessage]
|
|
860
|
+
# @raise [CLIConnectionError] if the stream ends without a ResultMessage
|
|
861
|
+
#
|
|
862
|
+
# @example
|
|
863
|
+
# puts ClaudeAgentSDK.ask('What is 2 + 2?').result
|
|
864
|
+
#
|
|
865
|
+
# @example Stream progress, keep the result
|
|
866
|
+
# result = ClaudeAgentSDK.ask('Refactor lib/foo.rb', options: options) do |message|
|
|
867
|
+
# puts message.text if message.is_a?(ClaudeAgentSDK::AssistantMessage)
|
|
868
|
+
# end
|
|
869
|
+
# puts result # => [result: success, 3 turns, 12.4s, $0.0421]
|
|
870
|
+
def self.ask(prompt, options: nil, transport: nil, &block)
|
|
871
|
+
result = nil
|
|
872
|
+
query(prompt: prompt, options: options, transport: transport) do |message|
|
|
873
|
+
result = message if message.is_a?(ResultMessage)
|
|
874
|
+
block&.call(message)
|
|
875
|
+
end
|
|
876
|
+
# The same class Query raises to a caller still waiting on the stream
|
|
877
|
+
# when it ends ("Control stream ended").
|
|
878
|
+
raise CLIConnectionError, 'Claude Code ended the conversation without a result message' unless result
|
|
879
|
+
|
|
880
|
+
result
|
|
881
|
+
end
|
|
882
|
+
|
|
705
883
|
# Client for bidirectional, interactive conversations with Claude Code
|
|
706
884
|
#
|
|
707
885
|
# This client provides full control over the conversation flow with support
|
|
@@ -971,6 +1149,12 @@ module ClaudeAgentSDK
|
|
|
971
1149
|
@query_handler.set_permission_mode(mode)
|
|
972
1150
|
end
|
|
973
1151
|
|
|
1152
|
+
# Ruby-style spelling of #set_permission_mode: `client.permission_mode = 'plan'`.
|
|
1153
|
+
# Delegates (rather than aliasing) so an override of #set_permission_mode applies to both.
|
|
1154
|
+
def permission_mode=(mode)
|
|
1155
|
+
set_permission_mode(mode)
|
|
1156
|
+
end
|
|
1157
|
+
|
|
974
1158
|
# Change the AI model during conversation
|
|
975
1159
|
# @param model [String, nil] Model name or nil for default
|
|
976
1160
|
def set_model(model)
|
|
@@ -978,6 +1162,11 @@ module ClaudeAgentSDK
|
|
|
978
1162
|
@query_handler.set_model(model)
|
|
979
1163
|
end
|
|
980
1164
|
|
|
1165
|
+
# Ruby-style spelling of #set_model: `client.model = 'claude-opus-5'`.
|
|
1166
|
+
def model=(model)
|
|
1167
|
+
set_model(model)
|
|
1168
|
+
end
|
|
1169
|
+
|
|
981
1170
|
# Reconnect a failed MCP server
|
|
982
1171
|
# @param server_name [String] Name of the MCP server to reconnect
|
|
983
1172
|
def reconnect_mcp_server(server_name)
|
|
@@ -1049,6 +1238,12 @@ module ClaudeAgentSDK
|
|
|
1049
1238
|
@query_handler.get_context_usage
|
|
1050
1239
|
end
|
|
1051
1240
|
|
|
1241
|
+
# Ruby-style spelling of #get_context_usage.
|
|
1242
|
+
# @return [Hash] Context usage response
|
|
1243
|
+
def context_usage
|
|
1244
|
+
get_context_usage
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1052
1247
|
# Get current MCP server connection status (only works with streaming mode)
|
|
1053
1248
|
# @return [Hash] MCP status information, including mcpServers list
|
|
1054
1249
|
def get_mcp_status
|
|
@@ -1056,6 +1251,12 @@ module ClaudeAgentSDK
|
|
|
1056
1251
|
@query_handler.get_mcp_status
|
|
1057
1252
|
end
|
|
1058
1253
|
|
|
1254
|
+
# Ruby-style spelling of #get_mcp_status.
|
|
1255
|
+
# @return [Hash] MCP status information, including mcpServers list
|
|
1256
|
+
def mcp_status
|
|
1257
|
+
get_mcp_status
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1059
1260
|
# Get server initialization info including available commands and output styles
|
|
1060
1261
|
# @return [Hash] Server info
|
|
1061
1262
|
def get_server_info
|
|
@@ -18,9 +18,9 @@ ClaudeAgentSDK.configure do |config|
|
|
|
18
18
|
# 'bypassPermissions' (unattended jobs in a sandbox only), or 'auto'.
|
|
19
19
|
# permission_mode: 'default',
|
|
20
20
|
|
|
21
|
-
# The CLI is found in vendor/claude
|
|
22
|
-
#
|
|
23
|
-
# cli_path:
|
|
21
|
+
# The CLI is found in Rails.root/vendor/claude (bin/rails claude_agent_sdk:install_cli),
|
|
22
|
+
# whatever the process cwd. To run a binary from somewhere else instead:
|
|
23
|
+
# cli_path: '/usr/local/bin/claude',
|
|
24
24
|
|
|
25
25
|
# OpenTelemetry tracing (Langfuse, Honeycomb, ...). A factory lambda gives
|
|
26
26
|
# every query/session its own observer — safe under Puma and job threads.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.36.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ya-luotao
|
|
@@ -138,6 +138,7 @@ files:
|
|
|
138
138
|
- lib/claude_agent_sdk/cli_installer.rb
|
|
139
139
|
- lib/claude_agent_sdk/command_builder.rb
|
|
140
140
|
- lib/claude_agent_sdk/configuration.rb
|
|
141
|
+
- lib/claude_agent_sdk/deprecation.rb
|
|
141
142
|
- lib/claude_agent_sdk/errors.rb
|
|
142
143
|
- lib/claude_agent_sdk/fiber_boundary.rb
|
|
143
144
|
- lib/claude_agent_sdk/instrumentation.rb
|