claude-agent-sdk 0.33.1 → 0.35.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 +93 -0
- data/README.md +54 -19
- data/docs/cli-installer.md +40 -9
- data/docs/client.md +27 -18
- data/docs/configuration.md +5 -5
- data/docs/errors.md +4 -3
- data/docs/mcp-servers.md +22 -0
- data/docs/observability.md +6 -0
- data/docs/rails.md +92 -51
- data/docs/sessions.md +66 -15
- data/lib/claude_agent_sdk/cli_installer.rb +34 -18
- data/lib/claude_agent_sdk/command_builder.rb +11 -3
- data/lib/claude_agent_sdk/configuration.rb +54 -2
- data/lib/claude_agent_sdk/errors.rb +11 -3
- data/lib/claude_agent_sdk/fiber_boundary.rb +42 -3
- data/lib/claude_agent_sdk/instrumentation/otel.rb +21 -2
- data/lib/claude_agent_sdk/query.rb +140 -59
- data/lib/claude_agent_sdk/railtie.rb +94 -0
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +46 -4
- data/lib/claude_agent_sdk/session_mutations.rb +39 -12
- data/lib/claude_agent_sdk/session_resume.rb +112 -39
- data/lib/claude_agent_sdk/session_store.rb +19 -3
- data/lib/claude_agent_sdk/session_summary.rb +5 -5
- data/lib/claude_agent_sdk/sessions.rb +123 -55
- data/lib/claude_agent_sdk/streaming.rb +0 -8
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +319 -54
- data/lib/claude_agent_sdk/tasks/claude_agent_sdk.rake +30 -0
- data/lib/claude_agent_sdk/tasks.rb +13 -0
- data/lib/claude_agent_sdk/testing/session_store_conformance.rb +13 -3
- data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +77 -18
- data/lib/claude_agent_sdk/types.rb +349 -39
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +53 -44
- data/lib/generators/claude_agent_sdk/install/install_generator.rb +63 -0
- data/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt +35 -0
- metadata +29 -13
|
@@ -18,6 +18,12 @@ module ClaudeAgentSDK
|
|
|
18
18
|
# background flush). This keeps adapter latency off the hot path during
|
|
19
19
|
# model streaming.
|
|
20
20
|
#
|
|
21
|
+
# At most ONE background drain task is live at a time. Frames arriving
|
|
22
|
+
# while it is busy stay in the pending buffer, and the drainer picks them up
|
|
23
|
+
# as one coalesced append when its current one completes — so a store slower
|
|
24
|
+
# than the frame rate costs buffered entries, never a task (plus detached
|
|
25
|
+
# batch) per frame, and #enqueue never suspends the read loop.
|
|
26
|
+
#
|
|
21
27
|
# Adapter failures are retried (MIRROR_APPEND_MAX_ATTEMPTS total) with short
|
|
22
28
|
# backoff; timeouts are not retried since the in-flight call may still land.
|
|
23
29
|
# Failures never raise — the local-disk transcript is already durable, so the
|
|
@@ -25,17 +31,19 @@ module ClaudeAgentSDK
|
|
|
25
31
|
# (which surfaces them as a MirrorErrorMessage). Adapters should dedupe by
|
|
26
32
|
# entry["uuid"] when present, since a retried batch may overlap a prior write.
|
|
27
33
|
#
|
|
28
|
-
# The semaphore serializes appends,
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
34
|
+
# The semaphore serializes appends (FIFO, and every drain detaches its batch
|
|
35
|
+
# immediately before queueing on it, so append order matches enqueue order),
|
|
36
|
+
# but a #send that exceeds send_timeout is abandoned (its worker thread keeps
|
|
37
|
+
# running) and the next drain proceeds, so two #append calls for the SAME
|
|
38
|
+
# key can briefly overlap. SessionStore#append must be thread-safe per key
|
|
39
|
+
# (see that method's contract). For adapters declaring
|
|
40
|
+
# `callback_scheduling -> :inline` the timed-out call is instead cancelled
|
|
41
|
+
# cooperatively (interrupted at its next suspension point, ensure runs) —
|
|
42
|
+
# but only on the adapter's own fiber; work the adapter offloaded may still
|
|
43
|
+
# land, so timeouts are not retried in either mode and the cancelled append
|
|
44
|
+
# may remain permanently HALF-applied in the store. The drop is surfaced
|
|
45
|
+
# (MirrorErrorMessage, batches_dropped?); the local transcript remains the
|
|
46
|
+
# source of truth.
|
|
39
47
|
class TranscriptMirrorBatcher
|
|
40
48
|
# Eager-flush thresholds (exposed for tests).
|
|
41
49
|
MAX_PENDING_ENTRIES = 500
|
|
@@ -72,6 +80,12 @@ module ClaudeAgentSDK
|
|
|
72
80
|
@pending = []
|
|
73
81
|
@pending_entries = 0
|
|
74
82
|
@pending_bytes = 0
|
|
83
|
+
# True while the background drain task is live; #enqueue spawns another
|
|
84
|
+
# only once it has exited (see #schedule_background_drain).
|
|
85
|
+
@background_drain_live = false
|
|
86
|
+
# Set when #close starts; from then on anything still in @pending counts
|
|
87
|
+
# as dropped (see #batches_dropped?).
|
|
88
|
+
@closed = false
|
|
75
89
|
# Batches that exhausted retries (or could not be keyed) and never
|
|
76
90
|
# reached the store. Written only under @lock; read cross-thread by
|
|
77
91
|
# #batches_dropped? (a plain Integer read is safe under the GVL).
|
|
@@ -86,12 +100,24 @@ module ClaudeAgentSDK
|
|
|
86
100
|
# mirror copy is incomplete. Consulted at teardown by the resume-from-store
|
|
87
101
|
# cleanup so the materialized temp dir (which then holds the only copy of
|
|
88
102
|
# the dropped turns) is preserved instead of deleted.
|
|
103
|
+
#
|
|
104
|
+
# Frames still buffered once #close has started count too. Query#close
|
|
105
|
+
# stops the read task (the background drainer's parent) right after
|
|
106
|
+
# #close returns, so a frame the read loop enqueued during the close
|
|
107
|
+
# window — or any below-threshold frame after it — is never appended.
|
|
108
|
+
# The old per-frame drain detached it into a parked task whose
|
|
109
|
+
# cancellation counted it; with coalescing it stays in @pending, so it is
|
|
110
|
+
# accounted here. Evaluated lazily: frames the drainer still manages to
|
|
111
|
+
# deliver before the check are not drops, and nothing past #close has to
|
|
112
|
+
# run for a stranded frame to be counted. (A plain ivar/Array#empty? read
|
|
113
|
+
# is safe cross-thread under the GVL, like @dropped_batches.)
|
|
89
114
|
def batches_dropped?
|
|
90
|
-
@dropped_batches.positive?
|
|
115
|
+
@dropped_batches.positive? || (@closed && !@pending.empty?)
|
|
91
116
|
end
|
|
92
117
|
|
|
93
118
|
# Buffer a frame; schedule an eager background flush if thresholds are
|
|
94
|
-
# exceeded
|
|
119
|
+
# exceeded and no background drain is already live (a live one will pick
|
|
120
|
+
# this frame up). Synchronous and fire-and-forget.
|
|
95
121
|
#
|
|
96
122
|
# +entries+ are deep-stringified because the transport parses CLI output
|
|
97
123
|
# with symbolized keys, but SessionStore entries are opaque JSON blobs that
|
|
@@ -109,12 +135,10 @@ module ClaudeAgentSDK
|
|
|
109
135
|
@pending << { file_path: file_path, entries: entries }
|
|
110
136
|
@pending_entries += entries.length
|
|
111
137
|
@pending_bytes += size
|
|
112
|
-
return unless
|
|
138
|
+
return unless over_threshold?
|
|
113
139
|
|
|
114
140
|
task = Async::Task.current?
|
|
115
|
-
|
|
116
|
-
# in-flight flush so append ordering holds. #drain never raises.
|
|
117
|
-
task ? task.async { drain } : drain
|
|
141
|
+
task ? schedule_background_drain(task) : drain
|
|
118
142
|
end
|
|
119
143
|
|
|
120
144
|
# Flush all pending entries, serialized after any in-flight eager flush.
|
|
@@ -122,8 +146,11 @@ module ClaudeAgentSDK
|
|
|
122
146
|
drain
|
|
123
147
|
end
|
|
124
148
|
|
|
125
|
-
# Final flush before teardown. Never raises.
|
|
149
|
+
# Final flush before teardown. Never raises. Bounded: it waits behind at
|
|
150
|
+
# most the in-flight append, and frames arriving after it detached are
|
|
151
|
+
# not chased (they count as dropped unless delivered; #batches_dropped?).
|
|
126
152
|
def close
|
|
153
|
+
@closed = true
|
|
127
154
|
flush
|
|
128
155
|
rescue StandardError => e
|
|
129
156
|
warn "Claude SDK: TranscriptMirrorBatcher close flush failed: #{e.message}"
|
|
@@ -131,6 +158,38 @@ module ClaudeAgentSDK
|
|
|
131
158
|
|
|
132
159
|
private
|
|
133
160
|
|
|
161
|
+
def over_threshold?
|
|
162
|
+
@pending_entries > @max_pending_entries || @pending_bytes > @max_pending_bytes
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Fire-and-forget on the reactor. The drainer loops until the buffer is
|
|
166
|
+
# back under the thresholds, so while it is live every later frame is
|
|
167
|
+
# simply buffered and coalesced into its next #drain — live background
|
|
168
|
+
# tasks <= 1 and detached batches <= 1 + one per #flush / #close caller
|
|
169
|
+
# parked on @lock, independent of frame rate and store latency.
|
|
170
|
+
#
|
|
171
|
+
# No lost wakeup: the loop's final over_threshold? check and the ensure
|
|
172
|
+
# clearing the flag run with no suspension point between them, so an
|
|
173
|
+
# #enqueue that saw the flag set is always observed by that check.
|
|
174
|
+
# Every drainer (this one, #flush, #close) keeps detach-then-acquire, so
|
|
175
|
+
# detach order == @lock FIFO order and append ordering holds. #drain
|
|
176
|
+
# never raises.
|
|
177
|
+
def schedule_background_drain(task)
|
|
178
|
+
return if @background_drain_live
|
|
179
|
+
|
|
180
|
+
@background_drain_live = true
|
|
181
|
+
task.async do
|
|
182
|
+
drain while over_threshold?
|
|
183
|
+
ensure
|
|
184
|
+
@background_drain_live = false
|
|
185
|
+
end
|
|
186
|
+
rescue StandardError
|
|
187
|
+
# Task#async raises synchronously (e.g. FinishedError) before the block
|
|
188
|
+
# runs; don't let a stuck flag disable background drains for good.
|
|
189
|
+
@background_drain_live = false
|
|
190
|
+
raise
|
|
191
|
+
end
|
|
192
|
+
|
|
134
193
|
# Detach the pending buffer, await any prior flush, then send. Detaching
|
|
135
194
|
# before acquiring the lock lets #enqueue keep accumulating into a fresh
|
|
136
195
|
# buffer while a prior flush is in flight. Never raises.
|