@tanstack/ai-client 0.22.0 → 0.23.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.
Files changed (72) hide show
  1. package/README.md +15 -1
  2. package/dist/esm/audio-recorder.js +190 -213
  3. package/dist/esm/audio-recorder.js.map +1 -1
  4. package/dist/esm/chat-client.d.ts +172 -3
  5. package/dist/esm/chat-client.js +1656 -1386
  6. package/dist/esm/chat-client.js.map +1 -1
  7. package/dist/esm/cleared-stream-tracker.d.ts +23 -0
  8. package/dist/esm/cleared-stream-tracker.js +97 -0
  9. package/dist/esm/cleared-stream-tracker.js.map +1 -0
  10. package/dist/esm/client-persistor.d.ts +25 -12
  11. package/dist/esm/client-persistor.js +260 -235
  12. package/dist/esm/client-persistor.js.map +1 -1
  13. package/dist/esm/connection-adapters.d.ts +231 -10
  14. package/dist/esm/connection-adapters.js +989 -574
  15. package/dist/esm/connection-adapters.js.map +1 -1
  16. package/dist/esm/devtools-noop.d.ts +1 -0
  17. package/dist/esm/devtools-noop.js +79 -139
  18. package/dist/esm/devtools-noop.js.map +1 -1
  19. package/dist/esm/devtools.d.ts +31 -1
  20. package/dist/esm/devtools.js +977 -1127
  21. package/dist/esm/devtools.js.map +1 -1
  22. package/dist/esm/events.js +224 -226
  23. package/dist/esm/events.js.map +1 -1
  24. package/dist/esm/generation-client.d.ts +145 -2
  25. package/dist/esm/generation-client.js +659 -321
  26. package/dist/esm/generation-client.js.map +1 -1
  27. package/dist/esm/generation-reconstruct.d.ts +21 -0
  28. package/dist/esm/generation-reconstruct.js +85 -0
  29. package/dist/esm/generation-reconstruct.js.map +1 -0
  30. package/dist/esm/generation-types.d.ts +289 -3
  31. package/dist/esm/generation-types.js +356 -13
  32. package/dist/esm/generation-types.js.map +1 -1
  33. package/dist/esm/index.d.ts +9 -4
  34. package/dist/esm/index.js +7 -39
  35. package/dist/esm/interrupt-manager.d.ts +77 -0
  36. package/dist/esm/interrupt-manager.js +787 -0
  37. package/dist/esm/interrupt-manager.js.map +1 -0
  38. package/dist/esm/mcp-app-bridge.js +56 -64
  39. package/dist/esm/mcp-app-bridge.js.map +1 -1
  40. package/dist/esm/realtime-client.js +366 -440
  41. package/dist/esm/realtime-client.js.map +1 -1
  42. package/dist/esm/response-stream.js +19 -26
  43. package/dist/esm/response-stream.js.map +1 -1
  44. package/dist/esm/sse-parser.js +44 -47
  45. package/dist/esm/sse-parser.js.map +1 -1
  46. package/dist/esm/sse-utils.js +8 -9
  47. package/dist/esm/sse-utils.js.map +1 -1
  48. package/dist/esm/storage-adapters.d.ts +62 -0
  49. package/dist/esm/storage-adapters.js +174 -0
  50. package/dist/esm/storage-adapters.js.map +1 -0
  51. package/dist/esm/types.d.ts +212 -10
  52. package/dist/esm/types.js +38 -7
  53. package/dist/esm/types.js.map +1 -1
  54. package/dist/esm/video-generation-client.d.ts +113 -2
  55. package/dist/esm/video-generation-client.js +665 -379
  56. package/dist/esm/video-generation-client.js.map +1 -1
  57. package/package.json +7 -7
  58. package/src/chat-client.ts +1079 -61
  59. package/src/cleared-stream-tracker.ts +151 -0
  60. package/src/client-persistor.ts +102 -33
  61. package/src/connection-adapters.ts +1185 -142
  62. package/src/devtools-noop.ts +4 -3
  63. package/src/devtools.ts +121 -3
  64. package/src/generation-client.ts +563 -13
  65. package/src/generation-reconstruct.ts +121 -0
  66. package/src/generation-types.ts +727 -3
  67. package/src/index.ts +56 -1
  68. package/src/interrupt-manager.ts +1440 -0
  69. package/src/storage-adapters.ts +242 -0
  70. package/src/types.ts +301 -9
  71. package/src/video-generation-client.ts +479 -13
  72. package/dist/esm/index.js.map +0 -1
@@ -0,0 +1,787 @@
1
+ import { INTERRUPT_BINDING_METADATA_KEY, INTERRUPT_BINDING_VERSION, canonicalInterruptJson, canonicalizeInterruptResolutions, cloneAndDeepFreezeJson, digestInterruptJson, hashSchemaInput, isStandardSchema, normalizeApprovalSchema } from "@tanstack/ai/client";
2
+ //#region src/interrupt-manager.ts
3
+ var itemErrorCodes = /* @__PURE__ */ new Set([
4
+ "invalid-payload",
5
+ "invalid-edited-args",
6
+ "invalid-tool-output",
7
+ "invalid-response-schema",
8
+ "unknown-interrupt",
9
+ "expired",
10
+ "stale",
11
+ "conflict",
12
+ "legacy-unsupported"
13
+ ]);
14
+ var batchErrorCodes = /* @__PURE__ */ new Set([
15
+ "incomplete-batch",
16
+ "item-validation-failed",
17
+ "unsupported-bulk-operation",
18
+ "async-resolver",
19
+ "inactive-transaction",
20
+ "mixed-provenance",
21
+ "transport",
22
+ "server",
23
+ "protocol",
24
+ "invalid-response-schema",
25
+ "expired",
26
+ "stale",
27
+ "conflict",
28
+ "legacy-submit-failed"
29
+ ]);
30
+ function isUnknownObject(value) {
31
+ return value !== null && typeof value === "object" && !Array.isArray(value);
32
+ }
33
+ function isLegacyApprovalMetadata(value) {
34
+ return isUnknownObject(value) && value["kind"] === "approval" && typeof value["toolName"] === "string" && "input" in value;
35
+ }
36
+ function isLegacyClientToolMetadata(value) {
37
+ return isUnknownObject(value) && value["kind"] === "client_tool" && typeof value["toolName"] === "string" && "input" in value;
38
+ }
39
+ /**
40
+ * Does this descriptor carry the pre-binding TanStack metadata marker?
41
+ *
42
+ * Descriptors emitted before the resume binding existed are still ours to
43
+ * resume, so they must not be mistaken for another producer's interrupt.
44
+ */
45
+ function isLegacyInterruptMetadata(interrupt) {
46
+ return isLegacyApprovalMetadata(interrupt.metadata) || isLegacyClientToolMetadata(interrupt.metadata);
47
+ }
48
+ function isBindingBase(value) {
49
+ return (value["v"] === void 0 || value["v"] === INTERRUPT_BINDING_VERSION) && typeof value["kind"] === "string" && typeof value["interruptId"] === "string" && typeof value["interruptedRunId"] === "string" && typeof value["generation"] === "number" && Number.isInteger(value["generation"]) && value["generation"] >= 0 && typeof value["responseSchemaHash"] === "string" && (value["expiresAt"] === void 0 || typeof value["expiresAt"] === "string" && Number.isFinite(Date.parse(value["expiresAt"])));
50
+ }
51
+ function readBinding(value) {
52
+ if (!isUnknownObject(value) || !isBindingBase(value)) return void 0;
53
+ const expiresAt = typeof value["expiresAt"] === "string" ? value["expiresAt"] : void 0;
54
+ if (value["kind"] === "generic") return {
55
+ v: INTERRUPT_BINDING_VERSION,
56
+ kind: "generic",
57
+ interruptId: String(value["interruptId"]),
58
+ interruptedRunId: String(value["interruptedRunId"]),
59
+ generation: Number(value["generation"]),
60
+ responseSchemaHash: String(value["responseSchemaHash"]),
61
+ ...expiresAt !== void 0 ? { expiresAt } : {}
62
+ };
63
+ if (value["kind"] === "client-tool-execution" && typeof value["toolName"] === "string" && typeof value["toolCallId"] === "string" && typeof value["outputSchemaHash"] === "string") return {
64
+ v: INTERRUPT_BINDING_VERSION,
65
+ kind: "client-tool-execution",
66
+ interruptId: String(value["interruptId"]),
67
+ interruptedRunId: String(value["interruptedRunId"]),
68
+ generation: Number(value["generation"]),
69
+ toolName: value["toolName"],
70
+ toolCallId: value["toolCallId"],
71
+ outputSchemaHash: value["outputSchemaHash"],
72
+ responseSchemaHash: String(value["responseSchemaHash"]),
73
+ ...expiresAt !== void 0 ? { expiresAt } : {}
74
+ };
75
+ if (value["kind"] === "tool-approval" && typeof value["toolName"] === "string" && typeof value["toolCallId"] === "string" && typeof value["inputSchemaHash"] === "string" && typeof value["approvalSchemaHash"] === "string" && "originalArgs" in value) return {
76
+ v: INTERRUPT_BINDING_VERSION,
77
+ kind: "tool-approval",
78
+ interruptId: String(value["interruptId"]),
79
+ interruptedRunId: String(value["interruptedRunId"]),
80
+ generation: Number(value["generation"]),
81
+ toolName: value["toolName"],
82
+ toolCallId: value["toolCallId"],
83
+ originalArgs: value["originalArgs"],
84
+ inputSchemaHash: value["inputSchemaHash"],
85
+ approvalSchemaHash: value["approvalSchemaHash"],
86
+ responseSchemaHash: String(value["responseSchemaHash"]),
87
+ ...expiresAt !== void 0 ? { expiresAt } : {}
88
+ };
89
+ }
90
+ function getDescriptorBinding(interrupt) {
91
+ const candidate = interrupt.metadata?.[INTERRUPT_BINDING_METADATA_KEY];
92
+ return readBinding(candidate);
93
+ }
94
+ /**
95
+ * Only used to route *legacy* (pre-binding) descriptors, which have no binding
96
+ * to classify off. Current descriptors are classified by their binding alone.
97
+ */
98
+ function isClientToolExecutionReason(reason) {
99
+ return reason === "tanstack:client_tool_execution" || reason === "client_tool_input";
100
+ }
101
+ function responseSchemaHash(interrupt) {
102
+ if (interrupt.responseSchema === void 0) return void 0;
103
+ try {
104
+ return digestInterruptJson(canonicalInterruptJson(interrupt.responseSchema));
105
+ } catch {
106
+ return;
107
+ }
108
+ }
109
+ function isPromiseLike(value) {
110
+ return value !== null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
111
+ }
112
+ function validateWithSchema(schema, value, code) {
113
+ if (schema === void 0) return {
114
+ valid: true,
115
+ payload: value
116
+ };
117
+ if (isStandardSchema(schema)) {
118
+ const result = schema["~standard"].validate(value);
119
+ const normalize = (validation) => {
120
+ if (!validation.issues) return {
121
+ valid: true,
122
+ payload: validation.value
123
+ };
124
+ return {
125
+ code,
126
+ message: validation.issues[0]?.message ?? "Schema validation failed."
127
+ };
128
+ };
129
+ return isPromiseLike(result) ? Promise.resolve(result).then(normalize) : normalize(result);
130
+ }
131
+ return {
132
+ valid: true,
133
+ payload: value
134
+ };
135
+ }
136
+ function isItemErrorCode(value) {
137
+ for (const code of itemErrorCodes) if (code === value) return true;
138
+ return false;
139
+ }
140
+ function isBatchErrorCode(value) {
141
+ for (const code of batchErrorCodes) if (code === value) return true;
142
+ return false;
143
+ }
144
+ function isSubmissionError(value) {
145
+ if (!isUnknownObject(value)) return false;
146
+ const scope = value["scope"];
147
+ const code = value["code"];
148
+ if (!(typeof code === "string" && typeof value["message"] === "string" && typeof value["retryable"] === "boolean" && typeof value["threadId"] === "string" && typeof value["interruptedRunId"] === "string" && typeof value["generation"] === "number")) return false;
149
+ if (scope === "item") return isItemErrorCode(code) && typeof value["interruptId"] === "string" && (value["source"] === "client" || value["source"] === "server");
150
+ return scope === "batch" && isBatchErrorCode(code) && Array.isArray(value["interruptIds"]) && value["interruptIds"].every((id) => typeof id === "string") && (value["source"] === "client" || value["source"] === "server" || value["source"] === "transport");
151
+ }
152
+ function readSubmissionErrors(error) {
153
+ if (isSubmissionError(error)) return [error];
154
+ if (!isUnknownObject(error) || !Array.isArray(error["errors"])) return [];
155
+ return error["errors"].every(isSubmissionError) ? error["errors"] : [];
156
+ }
157
+ function haveSameInterruptIds(left, right) {
158
+ if (left.length !== right.length) return false;
159
+ const sortedLeft = [...left].sort();
160
+ const sortedRight = [...right].sort();
161
+ return sortedLeft.every((id, index) => id === sortedRight[index]);
162
+ }
163
+ function haveSameBatchCorrelation(left, right) {
164
+ return left.threadId === right.threadId && left.interruptedRunId === right.interruptedRunId && left.generation === right.generation && haveSameInterruptIds(left.interruptIds, right.interruptIds);
165
+ }
166
+ function mergeSubmissionBatchErrors(current, previousSubmission, incoming) {
167
+ const replaceableIncoming = incoming.filter((error) => error.source !== "transport");
168
+ const isSuperseded = (candidate) => previousSubmission.includes(candidate) && replaceableIncoming.some((error) => haveSameBatchCorrelation(candidate, error));
169
+ const retainedRootErrors = current.filter((candidate) => !isSuperseded(candidate));
170
+ const retainedSubmissionRootErrors = previousSubmission.filter((candidate) => !replaceableIncoming.some((error) => haveSameBatchCorrelation(candidate, error)));
171
+ return Object.freeze({
172
+ rootErrors: Object.freeze([...retainedRootErrors, ...incoming]),
173
+ submissionRootErrors: Object.freeze([...retainedSubmissionRootErrors, ...replaceableIncoming])
174
+ });
175
+ }
176
+ function submissionErrorMatchesActiveBatch(error, submission) {
177
+ if (error.threadId !== submission.threadId || error.interruptedRunId !== submission.interruptedRunId || error.generation !== submission.generation) return false;
178
+ const interruptIds = submission.resolutions.map((resolution) => resolution.interruptId);
179
+ return error.scope === "item" ? interruptIds.includes(error.interruptId) : haveSameInterruptIds(error.interruptIds, interruptIds);
180
+ }
181
+ function genericBinding(interrupt, hydration, candidate) {
182
+ return cloneAndDeepFreezeJson({
183
+ v: INTERRUPT_BINDING_VERSION,
184
+ kind: "generic",
185
+ interruptId: interrupt.id,
186
+ interruptedRunId: hydration.interruptedRunId,
187
+ generation: hydration.generation,
188
+ responseSchemaHash: responseSchemaHash(interrupt) ?? candidate?.responseSchemaHash ?? "invalid",
189
+ ...interrupt.expiresAt !== void 0 ? { expiresAt: interrupt.expiresAt } : {}
190
+ });
191
+ }
192
+ function baseSnapshot(item, hydration, cancel, clearResolution) {
193
+ const descriptor = cloneAndDeepFreezeJson(item.descriptor);
194
+ const errors = item.error === void 0 ? Object.freeze([]) : Object.freeze([cloneAndDeepFreezeJson(item.error)]);
195
+ const error = errors[0];
196
+ return {
197
+ id: descriptor.id,
198
+ interruptId: descriptor.id,
199
+ reason: descriptor.reason,
200
+ ...descriptor.message !== void 0 ? { message: descriptor.message } : {},
201
+ ...descriptor.responseSchema !== void 0 ? { responseSchema: descriptor.responseSchema } : {},
202
+ ...descriptor.expiresAt !== void 0 ? { expiresAt: descriptor.expiresAt } : {},
203
+ ...descriptor.metadata !== void 0 ? { metadata: descriptor.metadata } : {},
204
+ threadId: hydration.threadId,
205
+ interruptedRunId: hydration.interruptedRunId,
206
+ generation: hydration.generation,
207
+ status: item.status,
208
+ errors,
209
+ ...error !== void 0 ? { error } : {},
210
+ canResolve: item.canResolve,
211
+ cancel,
212
+ clearResolution
213
+ };
214
+ }
215
+ var InterruptManager = class {
216
+ options;
217
+ hydration;
218
+ items = [];
219
+ snapshot = Object.freeze([]);
220
+ rootErrors = Object.freeze([]);
221
+ submissionRootErrors = Object.freeze([]);
222
+ state = Object.freeze({
223
+ interrupts: this.snapshot,
224
+ pendingInterrupts: this.snapshot,
225
+ interruptErrors: this.rootErrors,
226
+ resuming: false
227
+ });
228
+ activeTransaction;
229
+ retrySubmission;
230
+ resuming = false;
231
+ tools;
232
+ constructor(options) {
233
+ this.options = options;
234
+ this.tools = options.tools;
235
+ }
236
+ updateTools(tools) {
237
+ this.tools = tools;
238
+ }
239
+ hydrate(hydration) {
240
+ this.hydration = {
241
+ threadId: hydration.threadId,
242
+ interruptedRunId: hydration.interruptedRunId,
243
+ generation: hydration.generation,
244
+ interrupts: cloneAndDeepFreezeJson(hydration.interrupts)
245
+ };
246
+ this.items = hydration.interrupts.map((interrupt) => this.hydrateInterrupt(interrupt, hydration));
247
+ this.rootErrors = Object.freeze([]);
248
+ this.submissionRootErrors = Object.freeze([]);
249
+ this.retrySubmission = void 0;
250
+ this.resuming = false;
251
+ this.publish();
252
+ }
253
+ getInterrupts() {
254
+ return this.snapshot;
255
+ }
256
+ getState() {
257
+ return this.state;
258
+ }
259
+ getDescriptors() {
260
+ return this.hydration?.interrupts ?? Object.freeze([]);
261
+ }
262
+ reset(options) {
263
+ this.hydration = void 0;
264
+ this.items = [];
265
+ this.snapshot = Object.freeze([]);
266
+ if (options?.preserveRootErrors !== true) {
267
+ this.rootErrors = Object.freeze([]);
268
+ this.submissionRootErrors = Object.freeze([]);
269
+ }
270
+ this.retrySubmission = void 0;
271
+ this.resuming = false;
272
+ this.state = Object.freeze({
273
+ interrupts: this.snapshot,
274
+ pendingInterrupts: this.snapshot,
275
+ interruptErrors: this.rootErrors,
276
+ resuming: false
277
+ });
278
+ this.options.onChange?.();
279
+ }
280
+ getInterruptErrors() {
281
+ return this.rootErrors;
282
+ }
283
+ getResuming() {
284
+ return this.resuming;
285
+ }
286
+ resolve(resolution) {
287
+ this.assertRootMutable();
288
+ if (typeof resolution === "boolean") {
289
+ this.resolveBooleanBulk(resolution);
290
+ return;
291
+ }
292
+ this.resolveTransaction(resolution);
293
+ }
294
+ cancel() {
295
+ this.assertRootMutable();
296
+ this.invalidateRetry();
297
+ for (const item of this.items) {
298
+ item.validationGeneration++;
299
+ item.resolution = Object.freeze({
300
+ interruptId: item.descriptor.id,
301
+ status: "cancelled"
302
+ });
303
+ item.status = "staged";
304
+ item.error = void 0;
305
+ }
306
+ this.publish();
307
+ this.maybeSubmit();
308
+ }
309
+ retry() {
310
+ if (this.resuming) throw new Error("Interrupt submission is already active.");
311
+ const submission = this.retrySubmission;
312
+ if (!submission) {
313
+ this.addRootError("transport", "There is no retryable interrupt submission.", false);
314
+ return;
315
+ }
316
+ this.submitBatch(submission);
317
+ }
318
+ resolveClientToolOutput(toolCallId, output) {
319
+ const item = this.items.find((candidate) => candidate.kind === "client-tool-execution" && candidate.binding?.kind === "client-tool-execution" && candidate.binding.toolCallId === toolCallId || candidate.kind === "generic" && isClientToolExecutionReason(candidate.descriptor.reason) && candidate.descriptor.toolCallId === toolCallId && isLegacyClientToolMetadata(candidate.descriptor.metadata));
320
+ if (!item) return false;
321
+ this.resolveItem(item.descriptor.id, output);
322
+ return true;
323
+ }
324
+ resolveToolApprovalDecision(interruptId, approved) {
325
+ const item = this.items.find((candidate) => candidate.descriptor.id === interruptId && (candidate.kind === "tool-approval" || candidate.kind === "generic" && candidate.descriptor.reason === "approval_required" && isLegacyApprovalMetadata(candidate.descriptor.metadata)));
326
+ if (!item) return false;
327
+ this.resolveItem(item.descriptor.id, { approved });
328
+ return true;
329
+ }
330
+ hydrateInterrupt(descriptor, hydration) {
331
+ const interrupt = cloneAndDeepFreezeJson(descriptor);
332
+ const candidate = getDescriptorBinding(interrupt);
333
+ if (candidate === void 0 && !isLegacyInterruptMetadata(interrupt)) return {
334
+ descriptor: interrupt,
335
+ binding: void 0,
336
+ kind: "unbound",
337
+ status: "pending",
338
+ canResolve: false,
339
+ validationGeneration: 0
340
+ };
341
+ const correlated = candidate !== void 0 && candidate.interruptId === interrupt.id && candidate.interruptedRunId === hydration.interruptedRunId && candidate.generation === hydration.generation && candidate.responseSchemaHash === (responseSchemaHash(interrupt) ?? candidate.responseSchemaHash);
342
+ if (correlated && candidate.kind === "tool-approval") {
343
+ const tool = this.tools?.find((configured) => configured.name === candidate.toolName);
344
+ if (tool?.needsApproval === true && interrupt.toolCallId === candidate.toolCallId) try {
345
+ const approval = normalizeApprovalSchema(tool.approvalSchema, tool.inputSchema);
346
+ if (hashSchemaInput(tool.inputSchema) === candidate.inputSchemaHash && approval.approvalSchemaHash === candidate.approvalSchemaHash && approval.responseSchemaHash === candidate.responseSchemaHash) return {
347
+ descriptor: interrupt,
348
+ binding: cloneAndDeepFreezeJson(candidate),
349
+ kind: "tool-approval",
350
+ status: "pending",
351
+ canResolve: true,
352
+ tool,
353
+ validationGeneration: 0
354
+ };
355
+ } catch {}
356
+ }
357
+ if (correlated && candidate.kind === "client-tool-execution") {
358
+ const tool = this.tools?.find((configured) => configured.name === candidate.toolName);
359
+ if (tool !== void 0 && interrupt.toolCallId === candidate.toolCallId && hashSchemaInput(tool.outputSchema) === candidate.outputSchemaHash) return {
360
+ descriptor: interrupt,
361
+ binding: cloneAndDeepFreezeJson(candidate),
362
+ kind: "client-tool-execution",
363
+ status: "pending",
364
+ canResolve: true,
365
+ tool,
366
+ validationGeneration: 0
367
+ };
368
+ }
369
+ return {
370
+ descriptor: interrupt,
371
+ binding: genericBinding(interrupt, hydration, candidate),
372
+ kind: "generic",
373
+ status: "pending",
374
+ canResolve: true,
375
+ validationGeneration: 0
376
+ };
377
+ }
378
+ buildSnapshot(transaction) {
379
+ const hydration = this.requireHydration();
380
+ const next = this.items.filter((item) => item.kind !== "client-tool-execution" && item.status !== "submitting").map((item) => {
381
+ const base = baseSnapshot(item, hydration, () => this.cancelItem(item.descriptor.id, transaction), () => this.clearItem(item.descriptor.id, transaction));
382
+ if (item.kind === "unbound" || item.binding === void 0) {
383
+ const snapshot = {
384
+ ...base,
385
+ kind: "unbound",
386
+ canResolve: false
387
+ };
388
+ return Object.freeze(snapshot);
389
+ }
390
+ if (item.kind === "tool-approval" && item.binding.kind === "tool-approval") {
391
+ const binding = cloneAndDeepFreezeJson(item.binding);
392
+ const snapshot = {
393
+ ...base,
394
+ kind: "tool-approval",
395
+ binding,
396
+ toolName: item.binding.toolName,
397
+ toolCallId: item.binding.toolCallId,
398
+ originalArgs: cloneAndDeepFreezeJson(item.binding.originalArgs),
399
+ resolveInterrupt: (approved, options) => {
400
+ const details = isUnknownObject(options) ? options : void 0;
401
+ this.resolveItem(item.descriptor.id, {
402
+ approved,
403
+ ...approved && details?.["editedArgs"] !== void 0 ? { editedArgs: details["editedArgs"] } : {},
404
+ ...details?.["payload"] !== void 0 ? { payload: details["payload"] } : {}
405
+ }, transaction);
406
+ }
407
+ };
408
+ return Object.freeze(snapshot);
409
+ }
410
+ const boundGeneric = item.binding.kind === "generic" ? cloneAndDeepFreezeJson(item.binding) : cloneAndDeepFreezeJson({
411
+ v: INTERRUPT_BINDING_VERSION,
412
+ kind: "generic",
413
+ interruptId: item.descriptor.id,
414
+ interruptedRunId: hydration.interruptedRunId,
415
+ generation: hydration.generation,
416
+ responseSchemaHash: typeof item.binding.responseSchemaHash === "string" ? item.binding.responseSchemaHash : "none"
417
+ });
418
+ const snapshot = {
419
+ ...base,
420
+ kind: "generic",
421
+ binding: boundGeneric,
422
+ resolveInterrupt: (payload) => this.resolveItem(item.descriptor.id, payload, transaction)
423
+ };
424
+ return Object.freeze(snapshot);
425
+ });
426
+ return Object.freeze(next);
427
+ }
428
+ publish() {
429
+ if (!this.hydration) {
430
+ this.snapshot = Object.freeze([]);
431
+ this.state = Object.freeze({
432
+ interrupts: this.snapshot,
433
+ pendingInterrupts: this.snapshot,
434
+ interruptErrors: this.rootErrors,
435
+ resuming: this.resuming
436
+ });
437
+ this.options.onChange?.();
438
+ return;
439
+ }
440
+ this.snapshot = this.buildSnapshot();
441
+ this.state = Object.freeze({
442
+ interrupts: this.snapshot,
443
+ pendingInterrupts: this.snapshot,
444
+ interruptErrors: this.rootErrors,
445
+ resuming: this.resuming
446
+ });
447
+ this.options.onChange?.();
448
+ }
449
+ resolveItem(interruptId, payload, transaction) {
450
+ this.assertItemMutable(transaction);
451
+ const item = this.findItem(interruptId);
452
+ this.invalidateRetry();
453
+ if (!item.canResolve) {
454
+ item.status = "error";
455
+ item.error = this.itemError(interruptId, "invalid-response-schema", "The interrupt response schema is invalid and cannot be resolved.");
456
+ if (!transaction) this.publish();
457
+ return;
458
+ }
459
+ const validationGeneration = ++item.validationGeneration;
460
+ const validation = this.validateCandidate(item, payload);
461
+ if (isPromiseLike(validation)) {
462
+ item.status = "validating";
463
+ item.error = void 0;
464
+ if (!transaction) this.publish();
465
+ Promise.resolve(validation).then((result) => {
466
+ if (validationGeneration !== item.validationGeneration) return;
467
+ this.applyValidation(item, result, transaction);
468
+ }).catch((error) => {
469
+ if (validationGeneration !== item.validationGeneration) return;
470
+ this.applyValidation(item, {
471
+ code: this.validationCode(item),
472
+ message: error instanceof Error ? error.message : String(error)
473
+ }, transaction);
474
+ });
475
+ return;
476
+ }
477
+ this.applyValidation(item, validation, transaction);
478
+ }
479
+ cancelItem(interruptId, transaction) {
480
+ this.assertItemMutable(transaction);
481
+ const item = this.findItem(interruptId);
482
+ this.invalidateRetry();
483
+ item.validationGeneration++;
484
+ item.resolution = Object.freeze({
485
+ interruptId,
486
+ status: "cancelled"
487
+ });
488
+ item.status = "staged";
489
+ item.error = void 0;
490
+ if (!transaction) {
491
+ this.publish();
492
+ this.maybeSubmit();
493
+ }
494
+ }
495
+ clearItem(interruptId, transaction) {
496
+ this.assertItemMutable(transaction);
497
+ const item = this.findItem(interruptId);
498
+ this.invalidateRetry();
499
+ item.validationGeneration++;
500
+ item.resolution = void 0;
501
+ item.error = void 0;
502
+ item.status = "pending";
503
+ if (!transaction) this.publish();
504
+ }
505
+ maybeSubmit() {
506
+ const ours = this.items.filter((item) => item.kind !== "unbound");
507
+ if (ours.length === 0 || ours.some((item) => item.resolution === void 0 || item.status !== "staged")) return;
508
+ const hydration = this.requireHydration();
509
+ const canonical = canonicalizeInterruptResolutions(ours.map((item) => item.resolution).filter((item) => item !== void 0));
510
+ const submission = Object.freeze({
511
+ threadId: hydration.threadId,
512
+ interruptedRunId: hydration.interruptedRunId,
513
+ generation: hydration.generation,
514
+ resolutions: canonical.resolutions,
515
+ canonicalResolutions: canonical.canonicalResolutions,
516
+ fingerprint: canonical.fingerprint
517
+ });
518
+ this.submitBatch(submission);
519
+ }
520
+ applyValidation(item, result, transaction) {
521
+ if (!("valid" in result)) {
522
+ item.status = "error";
523
+ const itemError = this.itemError(item.descriptor.id, result.code, result.message, result.path);
524
+ item.error = itemError;
525
+ if (item.kind === "client-tool-execution") this.rootErrors = Object.freeze([...this.rootErrors.filter((error) => !(error.code === "item-validation-failed" && error.interruptIds.includes(item.descriptor.id))), Object.freeze({
526
+ scope: "batch",
527
+ code: "item-validation-failed",
528
+ message: itemError.message,
529
+ source: "client",
530
+ retryable: false,
531
+ interruptIds: Object.freeze([item.descriptor.id]),
532
+ threadId: itemError.threadId,
533
+ interruptedRunId: itemError.interruptedRunId,
534
+ generation: itemError.generation
535
+ })]);
536
+ if (!transaction) this.publish();
537
+ return;
538
+ }
539
+ item.resolution = cloneAndDeepFreezeJson({
540
+ interruptId: item.descriptor.id,
541
+ status: "resolved",
542
+ payload: result.payload
543
+ });
544
+ item.status = "staged";
545
+ item.error = void 0;
546
+ if (!transaction) {
547
+ this.publish();
548
+ this.maybeSubmit();
549
+ }
550
+ }
551
+ validateCandidate(item, payload) {
552
+ if (item.kind === "generic") return validateWithSchema(item.descriptor.responseSchema, payload, "invalid-payload");
553
+ if (item.kind === "client-tool-execution") return validateWithSchema(item.tool?.outputSchema, payload, "invalid-tool-output");
554
+ return this.validateApprovalCandidate(item, payload);
555
+ }
556
+ validateApprovalCandidate(item, payload) {
557
+ if (!isUnknownObject(payload) || typeof payload["approved"] !== "boolean") return {
558
+ code: "invalid-payload",
559
+ message: "Tool approval resolutions require an approved boolean."
560
+ };
561
+ const approved = payload["approved"];
562
+ const editedArgs = payload["editedArgs"];
563
+ if (!approved && editedArgs !== void 0) return {
564
+ code: "invalid-edited-args",
565
+ message: "Rejected tool approvals cannot edit tool arguments."
566
+ };
567
+ if (approved && editedArgs !== void 0) {
568
+ const editedValidation = validateWithSchema(item.tool?.inputSchema, editedArgs, "invalid-edited-args");
569
+ if (isPromiseLike(editedValidation)) return Promise.resolve(editedValidation).then((result) => "valid" in result ? this.validateApprovalPayload(item, payload, result.payload) : result);
570
+ if (!("valid" in editedValidation)) return editedValidation;
571
+ return this.validateApprovalPayload(item, payload, editedValidation.payload);
572
+ }
573
+ return this.validateApprovalPayload(item, payload, void 0);
574
+ }
575
+ validateApprovalPayload(item, envelope, validatedEditedArgs) {
576
+ const approved = envelope["approved"] === true;
577
+ const schema = this.approvalBranchSchema(item.tool, approved);
578
+ const branchPayload = envelope["payload"];
579
+ if (schema === void 0 && branchPayload !== void 0) return {
580
+ code: "invalid-payload",
581
+ message: "This approval branch does not accept a payload."
582
+ };
583
+ if (schema !== void 0 && branchPayload === void 0) return {
584
+ code: "invalid-payload",
585
+ message: "This approval branch requires a payload."
586
+ };
587
+ const validation = validateWithSchema(schema, branchPayload, "invalid-payload");
588
+ const buildEnvelope = (result) => {
589
+ if (!("valid" in result)) return result;
590
+ return {
591
+ valid: true,
592
+ payload: {
593
+ approved,
594
+ ...validatedEditedArgs !== void 0 ? { editedArgs: validatedEditedArgs } : {},
595
+ ...schema !== void 0 ? { payload: result.payload } : {}
596
+ }
597
+ };
598
+ };
599
+ return isPromiseLike(validation) ? Promise.resolve(validation).then(buildEnvelope) : buildEnvelope(validation);
600
+ }
601
+ approvalBranchSchema(tool, approved) {
602
+ const approvalSchema = tool?.approvalSchema;
603
+ if (!isUnknownObject(approvalSchema)) return approvalSchema;
604
+ if (!("approve" in approvalSchema || "reject" in approvalSchema)) return approvalSchema;
605
+ return approved ? approvalSchema["approve"] : approvalSchema["reject"];
606
+ }
607
+ validationCode(item) {
608
+ return item.kind === "client-tool-execution" ? "invalid-tool-output" : "invalid-payload";
609
+ }
610
+ resolveBooleanBulk(approved) {
611
+ const resolvable = this.items.filter((item) => item.kind !== "client-tool-execution");
612
+ if (!resolvable.every((item) => item.kind === "tool-approval" && this.approvalBranchSchema(item.tool, approved) === void 0) || resolvable.length === 0) {
613
+ this.addRootError("unsupported-bulk-operation", "Boolean bulk resolution requires payloadless tool approvals.", false);
614
+ return;
615
+ }
616
+ this.invalidateRetry();
617
+ for (const item of resolvable) {
618
+ item.validationGeneration++;
619
+ item.resolution = cloneAndDeepFreezeJson({
620
+ interruptId: item.descriptor.id,
621
+ status: "resolved",
622
+ payload: { approved }
623
+ });
624
+ item.status = "staged";
625
+ item.error = void 0;
626
+ }
627
+ this.publish();
628
+ this.maybeSubmit();
629
+ }
630
+ resolveTransaction(resolver) {
631
+ const checkpoints = this.items.map((item) => ({
632
+ status: item.status,
633
+ ...item.resolution !== void 0 ? { resolution: item.resolution } : {},
634
+ ...item.error !== void 0 ? { error: item.error } : {},
635
+ validationGeneration: item.validationGeneration
636
+ }));
637
+ const token = { active: true };
638
+ this.activeTransaction = token;
639
+ const stable = this.buildSnapshot(token);
640
+ let failure;
641
+ try {
642
+ for (const interrupt of stable) {
643
+ const result = resolver(interrupt);
644
+ if (result !== void 0) {
645
+ failure = {
646
+ code: isPromiseLike(result) ? "async-resolver" : "inactive-transaction",
647
+ message: isPromiseLike(result) ? "Interrupt transaction resolvers must be synchronous." : "Interrupt transaction resolvers must return literal undefined."
648
+ };
649
+ break;
650
+ }
651
+ }
652
+ if (failure === void 0 && this.items.some((item) => item.kind !== "client-tool-execution" && (item.resolution === void 0 || item.status !== "staged"))) failure = {
653
+ code: "incomplete-batch",
654
+ message: "Interrupt transaction did not resolve every item."
655
+ };
656
+ } catch (error) {
657
+ failure = {
658
+ code: "item-validation-failed",
659
+ message: error instanceof Error ? error.message : String(error)
660
+ };
661
+ } finally {
662
+ token.active = false;
663
+ this.activeTransaction = void 0;
664
+ }
665
+ if (failure) {
666
+ this.restoreCheckpoints(checkpoints);
667
+ this.addRootError(failure.code, failure.message, false);
668
+ return;
669
+ }
670
+ this.publish();
671
+ this.maybeSubmit();
672
+ }
673
+ restoreCheckpoints(checkpoints) {
674
+ this.items.forEach((item, index) => {
675
+ const checkpoint = checkpoints[index];
676
+ if (!checkpoint) return;
677
+ item.status = checkpoint.status;
678
+ item.resolution = checkpoint.resolution;
679
+ item.error = checkpoint.error;
680
+ item.validationGeneration = checkpoint.validationGeneration + 1;
681
+ });
682
+ this.publish();
683
+ }
684
+ assertItemMutable(transaction) {
685
+ if (transaction && !transaction.active) throw new Error("Interrupt transaction is inactive.");
686
+ if (this.activeTransaction && transaction !== this.activeTransaction) throw new Error("Interrupt transaction is inactive.");
687
+ if (this.resuming) throw new Error("Interrupts cannot be mutated while submitting.");
688
+ }
689
+ assertRootMutable() {
690
+ if (this.activeTransaction) throw new Error("Interrupt transaction is already active.");
691
+ if (this.resuming) throw new Error("Interrupts cannot be mutated while submitting.");
692
+ }
693
+ invalidateRetry() {
694
+ this.retrySubmission = void 0;
695
+ }
696
+ submitBatch(submission) {
697
+ this.resuming = true;
698
+ this.retrySubmission = void 0;
699
+ for (const item of this.items) item.status = "submitting";
700
+ this.publish();
701
+ this.performSubmission(submission);
702
+ }
703
+ async performSubmission(submission) {
704
+ try {
705
+ await this.options.submit(submission);
706
+ } catch (error) {
707
+ this.handleSubmissionFailure(error, submission);
708
+ } finally {
709
+ this.resuming = false;
710
+ this.publish();
711
+ }
712
+ }
713
+ handleSubmissionFailure(error, submission) {
714
+ const errors = readSubmissionErrors(error);
715
+ if (errors.length === 0) {
716
+ const message = error instanceof Error ? error.message : String(error);
717
+ this.addRootError("transport", message, true, "transport");
718
+ this.retrySubmission = submission;
719
+ for (const item of this.items) item.status = "error";
720
+ return;
721
+ }
722
+ const correlatedErrors = errors.filter((submissionError) => submissionErrorMatchesActiveBatch(submissionError, submission));
723
+ if (correlatedErrors.length !== errors.length) this.addRootError("protocol", "Interrupt submission errors did not match the active batch.", false);
724
+ let nonRetryable = false;
725
+ let retryable = false;
726
+ const batchErrors = [];
727
+ for (const submissionError of correlatedErrors) {
728
+ if (submissionError.code === "stale" || submissionError.code === "expired" || submissionError.code === "conflict") nonRetryable = true;
729
+ retryable ||= submissionError.retryable;
730
+ if (submissionError.scope === "item") {
731
+ const item = this.items.find((candidate) => candidate.descriptor.id === submissionError.interruptId);
732
+ if (item) {
733
+ item.status = "error";
734
+ item.error = cloneAndDeepFreezeJson(submissionError);
735
+ }
736
+ } else batchErrors.push(cloneAndDeepFreezeJson(submissionError));
737
+ }
738
+ const mergedBatchErrors = mergeSubmissionBatchErrors(this.rootErrors, this.submissionRootErrors, batchErrors);
739
+ this.rootErrors = mergedBatchErrors.rootErrors;
740
+ this.submissionRootErrors = mergedBatchErrors.submissionRootErrors;
741
+ for (const item of this.items) if (item.status === "submitting") item.status = "error";
742
+ this.retrySubmission = retryable && !nonRetryable ? submission : void 0;
743
+ }
744
+ addRootError(code, message, retryable, source = "client") {
745
+ const hydration = this.requireHydration();
746
+ this.rootErrors = Object.freeze([...this.rootErrors, Object.freeze({
747
+ scope: "batch",
748
+ code,
749
+ message,
750
+ source,
751
+ retryable,
752
+ interruptIds: Object.freeze(this.items.map((item) => item.descriptor.id)),
753
+ threadId: hydration.threadId,
754
+ interruptedRunId: hydration.interruptedRunId,
755
+ generation: hydration.generation
756
+ })]);
757
+ this.publish();
758
+ }
759
+ findItem(interruptId) {
760
+ const item = this.items.find((candidate) => candidate.descriptor.id === interruptId);
761
+ if (!item) throw new Error(`Unknown interrupt: ${interruptId}`);
762
+ return item;
763
+ }
764
+ requireHydration() {
765
+ if (!this.hydration) throw new Error("InterruptManager is not hydrated.");
766
+ return this.hydration;
767
+ }
768
+ itemError(interruptId, code, message, path) {
769
+ const hydration = this.requireHydration();
770
+ return Object.freeze({
771
+ scope: "item",
772
+ interruptId,
773
+ code,
774
+ message,
775
+ ...path !== void 0 ? { path: Object.freeze([...path]) } : {},
776
+ source: "client",
777
+ retryable: false,
778
+ threadId: hydration.threadId,
779
+ interruptedRunId: hydration.interruptedRunId,
780
+ generation: hydration.generation
781
+ });
782
+ }
783
+ };
784
+ //#endregion
785
+ export { InterruptManager };
786
+
787
+ //# sourceMappingURL=interrupt-manager.js.map