@langchain/langgraph-sdk 1.9.9 → 1.9.10
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.
- package/dist/client/stream/index.cjs +37 -5
- package/dist/client/stream/index.cjs.map +1 -1
- package/dist/client/stream/index.d.cts +7 -14
- package/dist/client/stream/index.d.cts.map +1 -1
- package/dist/client/stream/index.d.ts +7 -14
- package/dist/client/stream/index.d.ts.map +1 -1
- package/dist/client/stream/index.js +37 -5
- package/dist/client/stream/index.js.map +1 -1
- package/dist/client/stream/types.d.cts +8 -3
- package/dist/client/stream/types.d.cts.map +1 -1
- package/dist/client/stream/types.d.ts +8 -3
- package/dist/client/stream/types.d.ts.map +1 -1
- package/dist/headless-tools.cjs +0 -24
- package/dist/headless-tools.cjs.map +1 -1
- package/dist/headless-tools.d.cts.map +1 -1
- package/dist/headless-tools.d.ts.map +1 -1
- package/dist/headless-tools.js +1 -24
- package/dist/headless-tools.js.map +1 -1
- package/dist/stream/controller.cjs +156 -19
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts +113 -10
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts +113 -10
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +157 -20
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/index.d.cts +2 -2
- package/dist/stream/index.d.ts +2 -2
- package/dist/stream/message-metadata-tracker.cjs +1 -1
- package/dist/stream/message-metadata-tracker.cjs.map +1 -1
- package/dist/stream/message-metadata-tracker.d.cts +1 -1
- package/dist/stream/message-metadata-tracker.d.ts +1 -1
- package/dist/stream/message-metadata-tracker.js +1 -1
- package/dist/stream/message-metadata-tracker.js.map +1 -1
- package/dist/stream/submit-coordinator.cjs +45 -79
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.d.cts.map +1 -1
- package/dist/stream/submit-coordinator.d.ts.map +1 -1
- package/dist/stream/submit-coordinator.js +45 -79
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/dist/stream/types.d.cts +52 -30
- package/dist/stream/types.d.cts.map +1 -1
- package/dist/stream/types.d.ts +52 -30
- package/dist/stream/types.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ThreadStream } from "../client/stream/index.js";
|
|
2
2
|
import { StreamStore } from "./store.js";
|
|
3
|
-
import { RootSnapshot, StreamControllerOptions, StreamStopOptions, StreamSubmitOptions } from "./types.js";
|
|
3
|
+
import { RootSnapshot, StreamControllerOptions, StreamRespondAllOptions, StreamRespondOptions, StreamStopOptions, StreamSubmitOptions } from "./types.js";
|
|
4
4
|
import { ChannelRegistry } from "./channel-registry.js";
|
|
5
5
|
import { SubagentMap } from "./discovery/subagents.js";
|
|
6
6
|
import { SubgraphByNodeMap, SubgraphMap } from "./discovery/subgraphs.js";
|
|
@@ -61,9 +61,11 @@ declare class StreamController<StateType extends object = Record<string, unknown
|
|
|
61
61
|
*/
|
|
62
62
|
hydrate(threadId?: string | null): Promise<void>;
|
|
63
63
|
/**
|
|
64
|
-
* Submit input
|
|
64
|
+
* Submit input to the active thread.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
66
|
+
* To resume a pending interrupt, use {@link respond} instead.
|
|
67
|
+
*
|
|
68
|
+
* @param input - Input payload for a new run.
|
|
67
69
|
* @param options - Per-run config, metadata, multitask behavior, and callbacks.
|
|
68
70
|
*/
|
|
69
71
|
submit(input: unknown, options?: StreamSubmitOptions<StateType, ConfigurableType>): Promise<void>;
|
|
@@ -96,15 +98,116 @@ declare class StreamController<StateType extends object = Record<string, unknown
|
|
|
96
98
|
*/
|
|
97
99
|
clearQueue(): Promise<void>;
|
|
98
100
|
/**
|
|
99
|
-
* Respond to a pending protocol interrupt.
|
|
101
|
+
* Respond to a single pending protocol interrupt.
|
|
102
|
+
*
|
|
103
|
+
* When `options.interruptId` is omitted, resolution walks
|
|
104
|
+
* {@link ThreadStream.interrupts `thread.interrupts`} from newest to
|
|
105
|
+
* oldest and picks the first entry whose `interruptId` has not already
|
|
106
|
+
* been resolved by a prior `respond()` call. That entry may be at the
|
|
107
|
+
* root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
|
|
108
|
+
* This is **not** the same as {@link RootSnapshot.interrupts
|
|
109
|
+
* `rootStore.interrupts[0]`} / framework `stream.interrupt`, which only
|
|
110
|
+
* mirrors root-namespace interrupts for UI convenience.
|
|
111
|
+
*
|
|
112
|
+
* Omitting `interruptId` is fine when exactly one interrupt is pending.
|
|
113
|
+
* When several can be active (parallel subagents, fan-out, nested
|
|
114
|
+
* graphs), pass an explicit `interruptId` (and `namespace` for subgraph
|
|
115
|
+
* interrupts) so you resume the interrupt the user acted on.
|
|
116
|
+
*
|
|
117
|
+
* To resume several interrupts pending at the same checkpoint in one
|
|
118
|
+
* command, use {@link respondAll} — sequential single `respond()` calls
|
|
119
|
+
* would not work, since the first resume starts a run, leaving the
|
|
120
|
+
* others with no interrupted run to respond to.
|
|
121
|
+
*
|
|
122
|
+
* The server validates `namespace` against the pending interrupt. Root
|
|
123
|
+
* interrupts use `namespace: []` (the default when `namespace` is
|
|
124
|
+
* omitted). Subgraph interrupts require the exact tuple from
|
|
125
|
+
* `getThread()?.interrupts` — see the example below.
|
|
126
|
+
*
|
|
127
|
+
* @param response - Payload sent back to the interrupted namespace.
|
|
128
|
+
* @param options - Optional target (`interruptId` / `namespace`) and
|
|
129
|
+
* run-level `config` / `metadata` folded into the run that services
|
|
130
|
+
* the resume (model/user config, trigger source, test flags, …).
|
|
131
|
+
* Equivalent to the same fields on {@link StreamSubmitOptions}.
|
|
132
|
+
*
|
|
133
|
+
* @example Single pending interrupt (safe to omit a target)
|
|
134
|
+
* ```ts
|
|
135
|
+
* await controller.respond({ approved: true });
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* @example Carry run config / metadata onto the resume
|
|
139
|
+
* ```ts
|
|
140
|
+
* await controller.respond(
|
|
141
|
+
* { approved: true },
|
|
142
|
+
* { config: { configurable: { model: "gpt-4o" } }, metadata: { source: "ui" } },
|
|
143
|
+
* );
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @example Multiple root interrupts — target by id
|
|
147
|
+
* ```tsx
|
|
148
|
+
* for (const intr of stream.interrupts) {
|
|
149
|
+
* await stream.respond(decide(intr.value), { interruptId: intr.id! });
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* @example Subgraph interrupt — read `namespace` from the thread stream
|
|
154
|
+
* ```tsx
|
|
155
|
+
* const thread = stream.getThread();
|
|
156
|
+
* for (const entry of thread?.interrupts ?? []) {
|
|
157
|
+
* await stream.respond(buildResponse(entry.payload), {
|
|
158
|
+
* interruptId: entry.interruptId,
|
|
159
|
+
* namespace: entry.namespace,
|
|
160
|
+
* });
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* Each {@link InterruptPayload} on `thread.interrupts` mirrors an
|
|
165
|
+
* `input.requested` event: `{ interruptId, payload, namespace }`.
|
|
166
|
+
* Nested interrupts may appear here but not on `stream.interrupts`.
|
|
167
|
+
*/
|
|
168
|
+
respond(response: unknown, options?: StreamRespondOptions<ConfigurableType>): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* Resume several pending interrupts at the same checkpoint in a single
|
|
171
|
+
* command.
|
|
172
|
+
*
|
|
173
|
+
* Required when a run pauses on multiple interrupts simultaneously
|
|
174
|
+
* (e.g. parallel tool-authorization prompts): a single
|
|
175
|
+
* `Command({ resume })` carrying every interrupt's payload resumes them
|
|
176
|
+
* together. Sequential {@link respond} calls would fail because the
|
|
177
|
+
* first resume starts a run, leaving the rest with no interrupted run to
|
|
178
|
+
* respond to.
|
|
179
|
+
*
|
|
180
|
+
* `responsesById` maps each pending `interruptId` to the payload sent
|
|
181
|
+
* back to it, so different interrupts can receive different responses
|
|
182
|
+
* (approve one, deny another). To send the *same* payload to several
|
|
183
|
+
* interrupts, build the map with that value for each id, e.g.
|
|
184
|
+
* `Object.fromEntries(ids.map((id) => [id, response]))`.
|
|
185
|
+
*
|
|
186
|
+
* The server resumes by `interruptId`, so namespaces are resolved
|
|
187
|
+
* internally from `getThread()?.interrupts` and need not be supplied.
|
|
188
|
+
*
|
|
189
|
+
* @param responsesById - Map of pending `interruptId` to its response
|
|
190
|
+
* payload. Must contain at least one entry.
|
|
191
|
+
* @param options - Optional run-level `config` / `metadata` folded into
|
|
192
|
+
* the single run that services the batched resume. Equivalent to the
|
|
193
|
+
* same fields on {@link StreamSubmitOptions}.
|
|
194
|
+
*
|
|
195
|
+
* @example Distinct payloads per interrupt
|
|
196
|
+
* ```tsx
|
|
197
|
+
* await stream.respondAll({
|
|
198
|
+
* [interruptA.id]: { approved: true },
|
|
199
|
+
* [interruptB.id]: { approved: false },
|
|
200
|
+
* });
|
|
201
|
+
* ```
|
|
100
202
|
*
|
|
101
|
-
* @
|
|
102
|
-
*
|
|
203
|
+
* @example Same payload to every pending interrupt
|
|
204
|
+
* ```tsx
|
|
205
|
+
* await stream.respondAll(
|
|
206
|
+
* Object.fromEntries(stream.interrupts.map((i) => [i.id!, { approved: true }])),
|
|
207
|
+
* );
|
|
208
|
+
* ```
|
|
103
209
|
*/
|
|
104
|
-
|
|
105
|
-
interruptId: string;
|
|
106
|
-
namespace?: string[];
|
|
107
|
-
}): Promise<void>;
|
|
210
|
+
respondAll(responsesById: Record<string, unknown>, options?: StreamRespondAllOptions<ConfigurableType>): Promise<void>;
|
|
108
211
|
/**
|
|
109
212
|
* Dispose the active thread, subscriptions, registry entries, and listeners.
|
|
110
213
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;;AAyGA;cAAa,kBAAA,WAA6B,OAAA;;;AAkC1C;;;;;;;;;;;cAAa,gBAAA,4BACgB,MAAA,8EAEO,MAAA;EAAA;WAEzB,SAAA,EAAW,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,aAAA;EAAA,SAC/C,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,mBAAA,EAAqB,WAAA,CAAY,iBAAA;EAAA,SACjC,oBAAA,EAAsB,WAAA,CAAY,kBAAA;EAAA,SAClC,UAAA,EAAY,WAAA,CAAY,uBAAA,CAAwB,SAAA;EAAA,SAChD,QAAA,EAAU,eAAA;EAAA;;;;;EAyFnB,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EAmUF;;;;;;;EAAA,IAhOvC,gBAAA,CAAA,GAAoB,OAAA;EAgZS;;;;;;;;EA3W3B,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EA4hBpB;;;;;;;;EAnWf,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EAnaM;;;;;;EA6aH,IAAA,CAAK,OAAA,GAAU,iBAAA,GAAoB,OAAA;EA5aL;;;;EAgc9B,UAAA,CAAA,GAAc,OAAA;EA9bU;;;;;;;;;;;EA8fxB,YAAA,CAAa,EAAA,WAAa,OAAA;EAlaX;;;EAyaf,UAAA,CAAA,GAAc,OAAA;EAtUI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8YlB,OAAA,CACJ,QAAA,WACA,OAAA,GAAU,oBAAA,CAAqB,gBAAA,IAC9B,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyEG,UAAA,CACJ,aAAA,EAAe,MAAA,mBACf,OAAA,GAAU,uBAAA,CAAwB,gBAAA,IACjC,OAAA;;;;EAmCG,OAAA,CAAA,GAAW,OAAA;;;;;;;;;;;;;;;;EAyBjB,QAAA,CAAA;;;;;;EA4BA,SAAA,CAAA,GAAa,YAAA;;;;;;;;EAWb,eAAA,CACE,QAAA,GAAW,MAAA,EAAQ,YAAA;AAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ensureMessageInstances } from "../ui/messages.js";
|
|
2
2
|
import { ToolCallAssembler } from "../client/stream/handles/tools.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveInterruptTargetForHeadlessResume } from "../headless-tools.js";
|
|
4
4
|
import { StreamStore } from "./store.js";
|
|
5
5
|
import { ChannelRegistry } from "./channel-registry.js";
|
|
6
6
|
import { isInternalWorkNamespace, isLegacySubagentNamespace, isRootNamespace } from "./namespace.js";
|
|
@@ -186,14 +186,6 @@ var StreamController = class {
|
|
|
186
186
|
},
|
|
187
187
|
waitForRootPumpReady: () => this.#rootPumpReady,
|
|
188
188
|
awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),
|
|
189
|
-
buildResumeRunInput: (resume) => {
|
|
190
|
-
const thread = this.#thread;
|
|
191
|
-
if (thread == null) return null;
|
|
192
|
-
return buildResumeRunInput(resume, thread.interrupts, this.#resolvedInterrupts);
|
|
193
|
-
},
|
|
194
|
-
markInterruptResolved: (interruptId) => {
|
|
195
|
-
this.#resolvedInterrupts.add(interruptId);
|
|
196
|
-
},
|
|
197
189
|
onSubmitStart: () => {
|
|
198
190
|
this.#hydratedActiveInterruptIds = null;
|
|
199
191
|
this.#submitGeneration += 1;
|
|
@@ -419,9 +411,11 @@ var StreamController = class {
|
|
|
419
411
|
if (threadExists) thread.startLifecycleWatcher();
|
|
420
412
|
}
|
|
421
413
|
/**
|
|
422
|
-
* Submit input
|
|
414
|
+
* Submit input to the active thread.
|
|
415
|
+
*
|
|
416
|
+
* To resume a pending interrupt, use {@link respond} instead.
|
|
423
417
|
*
|
|
424
|
-
* @param input - Input payload for a new run
|
|
418
|
+
* @param input - Input payload for a new run.
|
|
425
419
|
* @param options - Per-run config, metadata, multitask behavior, and callbacks.
|
|
426
420
|
*/
|
|
427
421
|
async submit(input, options) {
|
|
@@ -505,25 +499,152 @@ var StreamController = class {
|
|
|
505
499
|
await this.#submitter.clearQueue();
|
|
506
500
|
}
|
|
507
501
|
/**
|
|
508
|
-
* Respond to a pending protocol interrupt.
|
|
502
|
+
* Respond to a single pending protocol interrupt.
|
|
503
|
+
*
|
|
504
|
+
* When `options.interruptId` is omitted, resolution walks
|
|
505
|
+
* {@link ThreadStream.interrupts `thread.interrupts`} from newest to
|
|
506
|
+
* oldest and picks the first entry whose `interruptId` has not already
|
|
507
|
+
* been resolved by a prior `respond()` call. That entry may be at the
|
|
508
|
+
* root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
|
|
509
|
+
* This is **not** the same as {@link RootSnapshot.interrupts
|
|
510
|
+
* `rootStore.interrupts[0]`} / framework `stream.interrupt`, which only
|
|
511
|
+
* mirrors root-namespace interrupts for UI convenience.
|
|
512
|
+
*
|
|
513
|
+
* Omitting `interruptId` is fine when exactly one interrupt is pending.
|
|
514
|
+
* When several can be active (parallel subagents, fan-out, nested
|
|
515
|
+
* graphs), pass an explicit `interruptId` (and `namespace` for subgraph
|
|
516
|
+
* interrupts) so you resume the interrupt the user acted on.
|
|
517
|
+
*
|
|
518
|
+
* To resume several interrupts pending at the same checkpoint in one
|
|
519
|
+
* command, use {@link respondAll} — sequential single `respond()` calls
|
|
520
|
+
* would not work, since the first resume starts a run, leaving the
|
|
521
|
+
* others with no interrupted run to respond to.
|
|
522
|
+
*
|
|
523
|
+
* The server validates `namespace` against the pending interrupt. Root
|
|
524
|
+
* interrupts use `namespace: []` (the default when `namespace` is
|
|
525
|
+
* omitted). Subgraph interrupts require the exact tuple from
|
|
526
|
+
* `getThread()?.interrupts` — see the example below.
|
|
527
|
+
*
|
|
528
|
+
* @param response - Payload sent back to the interrupted namespace.
|
|
529
|
+
* @param options - Optional target (`interruptId` / `namespace`) and
|
|
530
|
+
* run-level `config` / `metadata` folded into the run that services
|
|
531
|
+
* the resume (model/user config, trigger source, test flags, …).
|
|
532
|
+
* Equivalent to the same fields on {@link StreamSubmitOptions}.
|
|
533
|
+
*
|
|
534
|
+
* @example Single pending interrupt (safe to omit a target)
|
|
535
|
+
* ```ts
|
|
536
|
+
* await controller.respond({ approved: true });
|
|
537
|
+
* ```
|
|
538
|
+
*
|
|
539
|
+
* @example Carry run config / metadata onto the resume
|
|
540
|
+
* ```ts
|
|
541
|
+
* await controller.respond(
|
|
542
|
+
* { approved: true },
|
|
543
|
+
* { config: { configurable: { model: "gpt-4o" } }, metadata: { source: "ui" } },
|
|
544
|
+
* );
|
|
545
|
+
* ```
|
|
509
546
|
*
|
|
510
|
-
* @
|
|
511
|
-
*
|
|
547
|
+
* @example Multiple root interrupts — target by id
|
|
548
|
+
* ```tsx
|
|
549
|
+
* for (const intr of stream.interrupts) {
|
|
550
|
+
* await stream.respond(decide(intr.value), { interruptId: intr.id! });
|
|
551
|
+
* }
|
|
552
|
+
* ```
|
|
553
|
+
*
|
|
554
|
+
* @example Subgraph interrupt — read `namespace` from the thread stream
|
|
555
|
+
* ```tsx
|
|
556
|
+
* const thread = stream.getThread();
|
|
557
|
+
* for (const entry of thread?.interrupts ?? []) {
|
|
558
|
+
* await stream.respond(buildResponse(entry.payload), {
|
|
559
|
+
* interruptId: entry.interruptId,
|
|
560
|
+
* namespace: entry.namespace,
|
|
561
|
+
* });
|
|
562
|
+
* }
|
|
563
|
+
* ```
|
|
564
|
+
*
|
|
565
|
+
* Each {@link InterruptPayload} on `thread.interrupts` mirrors an
|
|
566
|
+
* `input.requested` event: `{ interruptId, payload, namespace }`.
|
|
567
|
+
* Nested interrupts may appear here but not on `stream.interrupts`.
|
|
512
568
|
*/
|
|
513
|
-
async respond(response,
|
|
569
|
+
async respond(response, options) {
|
|
514
570
|
if (this.#disposed || this.#thread == null) throw new Error("No active thread to respond to.");
|
|
515
|
-
const resolved =
|
|
516
|
-
interruptId:
|
|
517
|
-
namespace:
|
|
571
|
+
const resolved = options?.interruptId != null ? {
|
|
572
|
+
interruptId: options.interruptId,
|
|
573
|
+
namespace: options.namespace ?? [...ROOT_NAMESPACE]
|
|
518
574
|
} : this.#resolveInterruptForResume();
|
|
519
575
|
if (resolved == null) throw new Error("No pending interrupt to respond to.");
|
|
520
576
|
try {
|
|
521
577
|
await this.#thread.respondInput({
|
|
522
578
|
namespace: resolved.namespace,
|
|
523
579
|
interrupt_id: resolved.interruptId,
|
|
524
|
-
response
|
|
580
|
+
response,
|
|
581
|
+
config: options?.config,
|
|
582
|
+
metadata: options?.metadata
|
|
583
|
+
});
|
|
584
|
+
this.#markInterruptResolvedInRootStore(resolved.interruptId);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
if (this.#disposed && isAbortLikeError(error)) return;
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Resume several pending interrupts at the same checkpoint in a single
|
|
592
|
+
* command.
|
|
593
|
+
*
|
|
594
|
+
* Required when a run pauses on multiple interrupts simultaneously
|
|
595
|
+
* (e.g. parallel tool-authorization prompts): a single
|
|
596
|
+
* `Command({ resume })` carrying every interrupt's payload resumes them
|
|
597
|
+
* together. Sequential {@link respond} calls would fail because the
|
|
598
|
+
* first resume starts a run, leaving the rest with no interrupted run to
|
|
599
|
+
* respond to.
|
|
600
|
+
*
|
|
601
|
+
* `responsesById` maps each pending `interruptId` to the payload sent
|
|
602
|
+
* back to it, so different interrupts can receive different responses
|
|
603
|
+
* (approve one, deny another). To send the *same* payload to several
|
|
604
|
+
* interrupts, build the map with that value for each id, e.g.
|
|
605
|
+
* `Object.fromEntries(ids.map((id) => [id, response]))`.
|
|
606
|
+
*
|
|
607
|
+
* The server resumes by `interruptId`, so namespaces are resolved
|
|
608
|
+
* internally from `getThread()?.interrupts` and need not be supplied.
|
|
609
|
+
*
|
|
610
|
+
* @param responsesById - Map of pending `interruptId` to its response
|
|
611
|
+
* payload. Must contain at least one entry.
|
|
612
|
+
* @param options - Optional run-level `config` / `metadata` folded into
|
|
613
|
+
* the single run that services the batched resume. Equivalent to the
|
|
614
|
+
* same fields on {@link StreamSubmitOptions}.
|
|
615
|
+
*
|
|
616
|
+
* @example Distinct payloads per interrupt
|
|
617
|
+
* ```tsx
|
|
618
|
+
* await stream.respondAll({
|
|
619
|
+
* [interruptA.id]: { approved: true },
|
|
620
|
+
* [interruptB.id]: { approved: false },
|
|
621
|
+
* });
|
|
622
|
+
* ```
|
|
623
|
+
*
|
|
624
|
+
* @example Same payload to every pending interrupt
|
|
625
|
+
* ```tsx
|
|
626
|
+
* await stream.respondAll(
|
|
627
|
+
* Object.fromEntries(stream.interrupts.map((i) => [i.id!, { approved: true }])),
|
|
628
|
+
* );
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
async respondAll(responsesById, options) {
|
|
632
|
+
if (this.#disposed || this.#thread == null) throw new Error("No active thread to respond to.");
|
|
633
|
+
const entries = Object.entries(responsesById);
|
|
634
|
+
if (entries.length === 0) throw new Error("respondAll() requires at least one response.");
|
|
635
|
+
const pending = this.#thread.interrupts;
|
|
636
|
+
const responses = entries.map(([interruptId, response]) => ({
|
|
637
|
+
interrupt_id: interruptId,
|
|
638
|
+
response,
|
|
639
|
+
namespace: pending.find((entry) => entry.interruptId === interruptId)?.namespace ?? [...ROOT_NAMESPACE]
|
|
640
|
+
}));
|
|
641
|
+
try {
|
|
642
|
+
await this.#thread.respondInput({
|
|
643
|
+
responses,
|
|
644
|
+
config: options?.config,
|
|
645
|
+
metadata: options?.metadata
|
|
525
646
|
});
|
|
526
|
-
this.#
|
|
647
|
+
for (const { interrupt_id: interruptId } of responses) this.#markInterruptResolvedInRootStore(interruptId);
|
|
527
648
|
} catch (error) {
|
|
528
649
|
if (this.#disposed && isAbortLikeError(error)) return;
|
|
529
650
|
throw error;
|
|
@@ -1061,6 +1182,22 @@ var StreamController = class {
|
|
|
1061
1182
|
});
|
|
1062
1183
|
}
|
|
1063
1184
|
/**
|
|
1185
|
+
* Mark an interrupt resolved for replay filtering and mirror the
|
|
1186
|
+
* removal into the root snapshot the framework hooks read.
|
|
1187
|
+
*/
|
|
1188
|
+
#markInterruptResolvedInRootStore(interruptId) {
|
|
1189
|
+
this.#resolvedInterrupts.add(interruptId);
|
|
1190
|
+
this.rootStore.setState((s) => {
|
|
1191
|
+
const interrupts = s.interrupts.filter((entry) => entry.id !== interruptId);
|
|
1192
|
+
if (interrupts.length === s.interrupts.length && s.interrupt?.id !== interruptId) return s;
|
|
1193
|
+
return {
|
|
1194
|
+
...s,
|
|
1195
|
+
interrupts,
|
|
1196
|
+
interrupt: interrupts[0]
|
|
1197
|
+
};
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1064
1201
|
* Resolve on the next root-namespace terminal lifecycle event
|
|
1065
1202
|
* (`completed` / `failed` / `interrupted`) or on abort.
|
|
1066
1203
|
*
|