@threadplane/langgraph 0.0.57 → 0.0.58
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.
|
@@ -750,6 +750,8 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
750
750
|
if (resetState) {
|
|
751
751
|
invalidateQueueDrain();
|
|
752
752
|
abortController?.abort();
|
|
753
|
+
lastPayload = null;
|
|
754
|
+
lastOptions = undefined;
|
|
753
755
|
}
|
|
754
756
|
currentThreadId = id;
|
|
755
757
|
if (resetState) {
|
|
@@ -1011,7 +1013,7 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
1011
1013
|
subjects.toolProgress$.next([]);
|
|
1012
1014
|
toolProgressMap.clear();
|
|
1013
1015
|
canonicalMessageIds.clear();
|
|
1014
|
-
lastPayload = payload;
|
|
1016
|
+
lastPayload = payload ?? null;
|
|
1015
1017
|
lastOptions = opts;
|
|
1016
1018
|
// Tracks whether at least one stream event has been processed this run.
|
|
1017
1019
|
// Used to distinguish a mid-stream network interruption (kind:'interrupted')
|
|
@@ -1044,10 +1046,10 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
1044
1046
|
processEvent(event);
|
|
1045
1047
|
}
|
|
1046
1048
|
if (!isCurrentExecution(controller, attempt))
|
|
1047
|
-
return;
|
|
1049
|
+
return finishOutcome(attempt);
|
|
1048
1050
|
const outcome = await finalizeClosedAttempt(controller, attempt);
|
|
1049
1051
|
if (outcome === null)
|
|
1050
|
-
return;
|
|
1052
|
+
return finishOutcome(attempt);
|
|
1051
1053
|
if (!controller.signal.aborted) {
|
|
1052
1054
|
if (outcome !== 'error') {
|
|
1053
1055
|
subjects.status$.next(ResourceStatus.Resolved);
|
|
@@ -1058,12 +1060,13 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
1058
1060
|
durationMs: Date.now() - startedAt,
|
|
1059
1061
|
});
|
|
1060
1062
|
}
|
|
1063
|
+
return outcome;
|
|
1061
1064
|
}
|
|
1062
1065
|
catch (err) {
|
|
1063
1066
|
if (!isCurrentExecution(controller, attempt))
|
|
1064
|
-
return;
|
|
1067
|
+
return finishOutcome(attempt);
|
|
1065
1068
|
if (attempt.terminalOutcome)
|
|
1066
|
-
return;
|
|
1069
|
+
return attempt.terminalOutcome;
|
|
1067
1070
|
if (isAbortError(err) && userAbortedControllers.has(controller)) {
|
|
1068
1071
|
finalizeAttempt(attempt, 'aborted');
|
|
1069
1072
|
// User explicitly called stop() — treat as graceful idle, not an error.
|
|
@@ -1095,6 +1098,7 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
1095
1098
|
errorClass: agentRuntimeTelemetryErrorClass(err),
|
|
1096
1099
|
});
|
|
1097
1100
|
}
|
|
1101
|
+
return finishOutcome(attempt);
|
|
1098
1102
|
}
|
|
1099
1103
|
finally {
|
|
1100
1104
|
if (abortController === controller)
|
|
@@ -1407,9 +1411,9 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
1407
1411
|
submit: async (payload, opts) => {
|
|
1408
1412
|
if (opts?.multitaskStrategy === 'enqueue' && subjects.status$.value === ResourceStatus.Loading) {
|
|
1409
1413
|
await enqueueRun(payload, opts);
|
|
1410
|
-
return;
|
|
1414
|
+
return 'success';
|
|
1411
1415
|
}
|
|
1412
|
-
|
|
1416
|
+
return runStream(payload, opts);
|
|
1413
1417
|
},
|
|
1414
1418
|
stop: async () => {
|
|
1415
1419
|
invalidateQueueDrain();
|
|
@@ -1499,9 +1503,9 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
1499
1503
|
}
|
|
1500
1504
|
},
|
|
1501
1505
|
resubmitLast: async () => {
|
|
1502
|
-
if (lastPayload
|
|
1503
|
-
|
|
1504
|
-
|
|
1506
|
+
if (lastPayload === null)
|
|
1507
|
+
return 'not-started';
|
|
1508
|
+
return runStream(lastPayload, lastOptions, 'resubmit');
|
|
1505
1509
|
},
|
|
1506
1510
|
getReasoningDurationMs: (id) => {
|
|
1507
1511
|
const entry = reasoningTimingMap.get(id);
|
|
@@ -2364,6 +2368,24 @@ function mergeClientTools(payload, catalog) {
|
|
|
2364
2368
|
return payload;
|
|
2365
2369
|
return { ...payload, client_tools: catalog };
|
|
2366
2370
|
}
|
|
2371
|
+
/**
|
|
2372
|
+
* Merge A2UI client capabilities into a run payload under the
|
|
2373
|
+
* `a2ui_client_capabilities` state key. Same payload semantics as
|
|
2374
|
+
* {@link mergeClientTools}: null/undefined payloads (command resumes,
|
|
2375
|
+
* regenerates) and non-record payloads pass through untouched, and the
|
|
2376
|
+
* original object is never mutated. Because LangGraph thread state
|
|
2377
|
+
* persists across runs, the capabilities stamped by any run remain
|
|
2378
|
+
* readable by later runs on the same thread.
|
|
2379
|
+
*/
|
|
2380
|
+
function mergeA2uiClientCapabilities(payload, capabilities) {
|
|
2381
|
+
if (!capabilities)
|
|
2382
|
+
return payload;
|
|
2383
|
+
if (payload === null || payload === undefined)
|
|
2384
|
+
return payload;
|
|
2385
|
+
if (typeof payload !== 'object' || Array.isArray(payload))
|
|
2386
|
+
return payload;
|
|
2387
|
+
return { ...payload, a2ui_client_capabilities: capabilities };
|
|
2388
|
+
}
|
|
2367
2389
|
/**
|
|
2368
2390
|
* Prepend staged tool messages to a run payload's message list.
|
|
2369
2391
|
*
|
|
@@ -2396,22 +2418,25 @@ function mergeStagedToolMessages(payload, staged) {
|
|
|
2396
2418
|
* The backend ends the run without emitting a ToolMessage result for
|
|
2397
2419
|
* client tools, so `result` stays undefined on those entries.
|
|
2398
2420
|
* - settle(id, result): marks the call as resolved, writes the local result,
|
|
2399
|
-
* and
|
|
2400
|
-
* - flush():
|
|
2421
|
+
* and stages a ToolMessage with a deterministic ID without issuing a run.
|
|
2422
|
+
* - flush(): snapshots the whole staged group into ONE persistFn call without
|
|
2401
2423
|
* starting a run — the settlement path for tool groups that never continue.
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
2424
|
+
* Successful persistence acknowledges the captured entries; failed writes
|
|
2425
|
+
* retain them. When persistFn is absent, a non-empty flush rejects without
|
|
2426
|
+
* changing the staged results. Flushes are chained so entries settled after
|
|
2427
|
+
* one snapshot receive their own write. Concurrent persistence,
|
|
2428
|
+
* continuation, and ordinary submits may safely carry the same stable IDs.
|
|
2429
|
+
* - clearStagedToolMessages(): discards staged results on a thread switch and
|
|
2430
|
+
* advances the generation so late acknowledgments cannot affect new state.
|
|
2407
2431
|
* - resolve(id, result): settles the result, then issues a NEW run on the SAME
|
|
2408
|
-
* thread by calling submitFn with
|
|
2432
|
+
* thread by calling submitFn with a non-destructive ToolMessage snapshot:
|
|
2409
2433
|
* input: {
|
|
2410
2434
|
* messages: [{ type: 'tool', role: 'tool', tool_call_id: id, content }],
|
|
2411
2435
|
* client_tools: catalog(),
|
|
2412
2436
|
* }
|
|
2413
|
-
* The
|
|
2414
|
-
*
|
|
2437
|
+
* The snapshot remains staged unless that continuation reports success.
|
|
2438
|
+
* LangGraph's `add_messages` reducer reuses each stable message ID on safe
|
|
2439
|
+
* overlap or replay. Including `client_tools` ensures the model sees the
|
|
2415
2440
|
* full tool catalog on the continuation run.
|
|
2416
2441
|
*
|
|
2417
2442
|
* Catalog shipping: the catalog is NOT injected by this factory's
|
|
@@ -2426,8 +2451,8 @@ function createClientToolsCapability(submitFn, store, persistFn, currentThreadId
|
|
|
2426
2451
|
const resolvedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "resolvedIds" }] : []));
|
|
2427
2452
|
const toolMessageBuffer = [];
|
|
2428
2453
|
let flushInFlight;
|
|
2429
|
-
// Bumped whenever
|
|
2430
|
-
//
|
|
2454
|
+
// Bumped whenever a thread reset discards staged state. Every snapshot keeps
|
|
2455
|
+
// its generation, making acknowledgments from the prior thread no-ops.
|
|
2431
2456
|
let bufferGeneration = 0;
|
|
2432
2457
|
// Tool calls belonging to threads we have left. A handler still running when
|
|
2433
2458
|
// the user switches threads settles AFTER the switch, by which point both the
|
|
@@ -2482,62 +2507,76 @@ function createClientToolsCapability(submitFn, store, persistFn, currentThreadId
|
|
|
2482
2507
|
// shape used in buildSubmitUpdate (agent.fn.ts line 732).
|
|
2483
2508
|
toolMessageBuffer.push({
|
|
2484
2509
|
threadId: currentThreadIdFn?.() ?? null,
|
|
2485
|
-
message: {
|
|
2510
|
+
message: {
|
|
2511
|
+
id: `client-tool-result-${id}`,
|
|
2512
|
+
type: 'tool',
|
|
2513
|
+
role: 'tool',
|
|
2514
|
+
tool_call_id: id,
|
|
2515
|
+
content,
|
|
2516
|
+
},
|
|
2486
2517
|
});
|
|
2487
2518
|
}
|
|
2488
2519
|
/**
|
|
2489
|
-
*
|
|
2490
|
-
*
|
|
2491
|
-
*
|
|
2520
|
+
* Returns a non-destructive snapshot of entries still valid for the thread a
|
|
2521
|
+
* write would land on right now. Captured entries remain staged, so
|
|
2522
|
+
* overlapping operations may carry the same deterministic message IDs.
|
|
2523
|
+
* Acknowledgment removes only the exact captured entries while the snapshot
|
|
2524
|
+
* generation is current. An entry stamped for another thread is dropped
|
|
2525
|
+
* rather than misdelivered.
|
|
2492
2526
|
*
|
|
2493
2527
|
* A null on either side means "thread not tracked yet" (no threadId option
|
|
2494
2528
|
* and no run has reported one); those are kept, since there is no evidence of
|
|
2495
2529
|
* a switch and dropping them would lose results on untracked transports.
|
|
2496
2530
|
*/
|
|
2497
|
-
function
|
|
2531
|
+
function snapshotToolMessages() {
|
|
2498
2532
|
const current = currentThreadIdFn?.() ?? null;
|
|
2499
|
-
|
|
2500
|
-
|
|
2533
|
+
for (let index = toolMessageBuffer.length - 1; index >= 0; index -= 1) {
|
|
2534
|
+
const entry = toolMessageBuffer[index];
|
|
2501
2535
|
const stale = entry.threadId !== null && current !== null && entry.threadId !== current;
|
|
2502
2536
|
if (stale) {
|
|
2503
2537
|
console.warn(`Discarding a client tool result staged for thread ${entry.threadId}; ` +
|
|
2504
2538
|
`the active thread is now ${current}.`);
|
|
2539
|
+
toolMessageBuffer.splice(index, 1);
|
|
2505
2540
|
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2541
|
+
}
|
|
2542
|
+
const generation = bufferGeneration;
|
|
2543
|
+
const entries = [...toolMessageBuffer];
|
|
2544
|
+
const messages = entries.map(({ message }) => ({ ...message }));
|
|
2545
|
+
let acknowledged = false;
|
|
2546
|
+
return {
|
|
2547
|
+
generation,
|
|
2548
|
+
messages,
|
|
2549
|
+
acknowledge() {
|
|
2550
|
+
if (acknowledged || generation !== bufferGeneration)
|
|
2551
|
+
return;
|
|
2552
|
+
acknowledged = true;
|
|
2553
|
+
for (const entry of entries) {
|
|
2554
|
+
const index = toolMessageBuffer.indexOf(entry);
|
|
2555
|
+
if (index !== -1)
|
|
2556
|
+
toolMessageBuffer.splice(index, 1);
|
|
2557
|
+
}
|
|
2558
|
+
},
|
|
2559
|
+
};
|
|
2508
2560
|
}
|
|
2509
2561
|
/**
|
|
2510
|
-
* Persist one
|
|
2511
|
-
*
|
|
2512
|
-
* nothing about an in-flight write, so anything left staged across the await
|
|
2513
|
-
* could be re-sent (a duplicate ToolMessage for one tool_call_id) or removed
|
|
2514
|
-
* by the wrong index (dropping a result that was never persisted).
|
|
2562
|
+
* Persist one snapshot. Only that snapshot is acknowledged after persistence
|
|
2563
|
+
* succeeds; failure leaves its exact entries staged for retry or submit.
|
|
2515
2564
|
*/
|
|
2516
2565
|
function runFlush() {
|
|
2517
|
-
|
|
2566
|
+
const batch = snapshotToolMessages();
|
|
2567
|
+
if (batch.messages.length === 0)
|
|
2518
2568
|
return Promise.resolve();
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
.
|
|
2526
|
-
// Re-stage at the FRONT so ordering is preserved for the next drain —
|
|
2527
|
-
// unless the buffer was cleared meanwhile (thread switch), in which
|
|
2528
|
-
// case these results belong to a thread we have left.
|
|
2529
|
-
if (generation === bufferGeneration) {
|
|
2530
|
-
toolMessageBuffer.unshift(...staged);
|
|
2531
|
-
}
|
|
2532
|
-
console.warn(`Client tool flush failed; ${batch.length} result(s) remain staged for the next run.`, err);
|
|
2569
|
+
if (!persistFn) {
|
|
2570
|
+
return Promise.reject(new Error('Cannot flush staged client tool results. ' +
|
|
2571
|
+
'Custom LangGraph transports using terminal client tools must implement updateState().'));
|
|
2572
|
+
}
|
|
2573
|
+
return persistFn(batch.messages)
|
|
2574
|
+
.then(() => {
|
|
2575
|
+
batch.acknowledge();
|
|
2533
2576
|
})
|
|
2534
|
-
.
|
|
2535
|
-
|
|
2536
|
-
if (flushInFlight === inFlight)
|
|
2537
|
-
flushInFlight = undefined;
|
|
2577
|
+
.catch((err) => {
|
|
2578
|
+
console.warn(`Client tool flush failed; ${batch.messages.length} result(s) remain staged for the next run.`, err);
|
|
2538
2579
|
});
|
|
2539
|
-
flushInFlight = inFlight;
|
|
2540
|
-
return inFlight;
|
|
2541
2580
|
}
|
|
2542
2581
|
const capability = {
|
|
2543
2582
|
catalog,
|
|
@@ -2548,9 +2587,8 @@ function createClientToolsCapability(submitFn, store, persistFn, currentThreadId
|
|
|
2548
2587
|
settle(id, result) {
|
|
2549
2588
|
settleResult(id, result);
|
|
2550
2589
|
},
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
return takeStagedForCurrentThread().map((entry) => entry.message);
|
|
2590
|
+
snapshotToolMessages() {
|
|
2591
|
+
return snapshotToolMessages();
|
|
2554
2592
|
},
|
|
2555
2593
|
/**
|
|
2556
2594
|
* Discard everything staged. Called when the active thread changes: a
|
|
@@ -2564,36 +2602,43 @@ function createClientToolsCapability(submitFn, store, persistFn, currentThreadId
|
|
|
2564
2602
|
for (const toolCall of store.toolCalls())
|
|
2565
2603
|
retiredToolCallIds.add(toolCall.id);
|
|
2566
2604
|
toolMessageBuffer.length = 0;
|
|
2567
|
-
// Invalidate
|
|
2568
|
-
// old thread's messages into the new thread's buffer.
|
|
2605
|
+
// Invalidate every outstanding snapshot acknowledgment from the old thread.
|
|
2569
2606
|
bufferGeneration += 1;
|
|
2570
2607
|
},
|
|
2571
2608
|
flush() {
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2609
|
+
// Only this method owns the queue tail. Results settled after an active
|
|
2610
|
+
// snapshot need their own write, and every caller must remain behind all
|
|
2611
|
+
// callers already queued. The chain terminates when a fresh snapshot is
|
|
2612
|
+
// empty.
|
|
2613
|
+
const queued = flushInFlight
|
|
2614
|
+
? flushInFlight.then(() => runFlush(), () => runFlush())
|
|
2615
|
+
: runFlush();
|
|
2616
|
+
flushInFlight = queued;
|
|
2617
|
+
const clearTail = () => {
|
|
2618
|
+
if (flushInFlight === queued)
|
|
2619
|
+
flushInFlight = undefined;
|
|
2620
|
+
};
|
|
2621
|
+
// Both handlers return normally, so this bookkeeping branch cannot
|
|
2622
|
+
// create an unhandled rejection when the caller observes `queued`.
|
|
2623
|
+
void queued.then(clearTail, clearTail);
|
|
2624
|
+
return queued;
|
|
2586
2625
|
},
|
|
2587
2626
|
resolve(id, result) {
|
|
2588
2627
|
settleResult(id, result);
|
|
2589
|
-
// Issue a new run
|
|
2590
|
-
//
|
|
2591
|
-
//
|
|
2628
|
+
// Issue a new run with a non-destructive snapshot. The exact batch is
|
|
2629
|
+
// acknowledged only when that continuation succeeds; failures retain it
|
|
2630
|
+
// for retry. Stable IDs make overlap with flush or submit safe under
|
|
2631
|
+
// LangGraph's add_messages reducer. `client_tools` keeps the full catalog
|
|
2632
|
+
// visible to the continuation.
|
|
2633
|
+
const batch = snapshotToolMessages();
|
|
2592
2634
|
const toolPayload = {
|
|
2593
|
-
messages:
|
|
2635
|
+
messages: batch.messages,
|
|
2594
2636
|
client_tools: catalog(),
|
|
2595
2637
|
};
|
|
2596
|
-
void submitFn(toolPayload)
|
|
2638
|
+
void submitFn(toolPayload, undefined, batch).then((outcome) => {
|
|
2639
|
+
if (outcome === 'success')
|
|
2640
|
+
batch.acknowledge();
|
|
2641
|
+
});
|
|
2597
2642
|
},
|
|
2598
2643
|
};
|
|
2599
2644
|
return capability;
|
|
@@ -2709,6 +2754,7 @@ function agent(options) {
|
|
|
2709
2754
|
// seam lives here. A holder keeps the binding itself a `const` while its
|
|
2710
2755
|
// member is filled in later.
|
|
2711
2756
|
const clientToolStaging = {};
|
|
2757
|
+
let retryableToolMessageBatch;
|
|
2712
2758
|
function resetDerivedThreadState() {
|
|
2713
2759
|
status$.next(ResourceStatus.Idle);
|
|
2714
2760
|
error$.next(undefined);
|
|
@@ -2920,10 +2966,14 @@ function agent(options) {
|
|
|
2920
2966
|
// updateState() silently no-ops when the transport has no updateState, so
|
|
2921
2967
|
// only supply a persist function when the effective transport supports it —
|
|
2922
2968
|
// an omitted transport means the bridge builds a FetchStreamTransport, which
|
|
2923
|
-
// does. When persistFn is undefined, flush()
|
|
2924
|
-
//
|
|
2969
|
+
// does. When persistFn is undefined, a non-empty flush() rejects while the
|
|
2970
|
+
// results stay staged; an ordinary non-null submit remains their in-memory
|
|
2971
|
+
// fallback path.
|
|
2925
2972
|
const canPersistToolMessages = !transport || typeof transport.updateState === 'function';
|
|
2926
|
-
const clientToolsCap = createClientToolsCapability((payload, opts) =>
|
|
2973
|
+
const clientToolsCap = createClientToolsCapability((payload, opts, batch) => {
|
|
2974
|
+
retryableToolMessageBatch = batch;
|
|
2975
|
+
return manager.submit(payload, opts);
|
|
2976
|
+
}, {
|
|
2927
2977
|
toolCalls: toolCallsNeutral,
|
|
2928
2978
|
isLoading,
|
|
2929
2979
|
applyClientResult: (id, patch) => clientResultOverrides.update((m) => new Map(m).set(id, patch)),
|
|
@@ -2942,7 +2992,16 @@ function agent(options) {
|
|
|
2942
2992
|
// Stamps each staged result with the thread it was settled on, so a write
|
|
2943
2993
|
// can never land on a thread the user has since moved to.
|
|
2944
2994
|
() => manager.currentThreadId);
|
|
2945
|
-
clientToolStaging.clear = () =>
|
|
2995
|
+
clientToolStaging.clear = () => {
|
|
2996
|
+
retryableToolMessageBatch = undefined;
|
|
2997
|
+
clientToolsCap.clearStagedToolMessages();
|
|
2998
|
+
};
|
|
2999
|
+
async function resubmitWithToolRecovery() {
|
|
3000
|
+
const batch = retryableToolMessageBatch;
|
|
3001
|
+
const outcome = await manager.resubmitLast();
|
|
3002
|
+
if (outcome === 'success')
|
|
3003
|
+
batch?.acknowledge();
|
|
3004
|
+
}
|
|
2946
3005
|
return {
|
|
2947
3006
|
// ── Runtime-neutral surface (AgentWithHistory) ────────────────────────
|
|
2948
3007
|
messages: messagesNeutral,
|
|
@@ -2956,7 +3015,7 @@ function agent(options) {
|
|
|
2956
3015
|
events$,
|
|
2957
3016
|
history: historyNeutral,
|
|
2958
3017
|
messageCheckpoints: messageCheckpointsSig,
|
|
2959
|
-
submit: (input, opts) => {
|
|
3018
|
+
submit: async (input, opts) => {
|
|
2960
3019
|
// Lifecycle: first submit with no existing threadId → thread create.
|
|
2961
3020
|
if (lcThreadCreatedAt() === null && lastThreadId == null) {
|
|
2962
3021
|
lcThreadCreatedAt.set(Date.now());
|
|
@@ -2970,25 +3029,33 @@ function agent(options) {
|
|
|
2970
3029
|
// backend middleware can merge them into the model's tool list. Null
|
|
2971
3030
|
// payloads (regenerate re-runs, command resumes) are left unchanged.
|
|
2972
3031
|
//
|
|
2973
|
-
//
|
|
2974
|
-
//
|
|
2975
|
-
//
|
|
2976
|
-
//
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
3032
|
+
// Snapshot results settled but not yet durable (flush unavailable or a
|
|
3033
|
+
// prior flush failed) so they ride along without being forgotten. The
|
|
3034
|
+
// exact snapshot is acknowledged only when this operation succeeds;
|
|
3035
|
+
// overlaps may safely carry the same deterministic message IDs. A null
|
|
3036
|
+
// payload cannot carry results, so it leaves staging unchanged.
|
|
3037
|
+
const batch = request.payload === null || request.payload === undefined
|
|
3038
|
+
? undefined
|
|
3039
|
+
: clientToolsCap.snapshotToolMessages();
|
|
3040
|
+
const staged = batch?.messages ?? [];
|
|
2980
3041
|
const withStaged = staged.length > 0
|
|
2981
3042
|
? mergeStagedToolMessages(request.payload, staged)
|
|
2982
3043
|
: request.payload;
|
|
2983
|
-
const payload = mergeClientTools(withStaged, clientToolsCap.catalog());
|
|
2984
|
-
|
|
3044
|
+
const payload = mergeA2uiClientCapabilities(mergeClientTools(withStaged, clientToolsCap.catalog()), options.a2uiClientCapabilities);
|
|
3045
|
+
const createsQueuedRun = request.options?.multitaskStrategy === 'enqueue' && isLoading();
|
|
3046
|
+
if (!createsQueuedRun) {
|
|
3047
|
+
retryableToolMessageBatch = batch;
|
|
3048
|
+
}
|
|
3049
|
+
const outcome = await manager.submit(payload, request.options);
|
|
3050
|
+
if (outcome === 'success')
|
|
3051
|
+
batch?.acknowledge();
|
|
2985
3052
|
},
|
|
2986
3053
|
stop: () => manager.stop(),
|
|
2987
3054
|
retry: async () => {
|
|
2988
3055
|
if (isLoading())
|
|
2989
3056
|
return; // no-op while a run is in flight
|
|
2990
3057
|
error$.next(undefined); // clear the error before re-running
|
|
2991
|
-
await
|
|
3058
|
+
await resubmitWithToolRecovery();
|
|
2992
3059
|
},
|
|
2993
3060
|
clientTools: clientToolsCap,
|
|
2994
3061
|
regenerate: async (assistantMessageIndex) => {
|
|
@@ -3045,6 +3112,7 @@ function agent(options) {
|
|
|
3045
3112
|
// at `__start__`, this resumes at the entry node and produces a fresh
|
|
3046
3113
|
// assistant message — the trailing user message becomes the active
|
|
3047
3114
|
// prompt without being re-appended.
|
|
3115
|
+
retryableToolMessageBatch = undefined;
|
|
3048
3116
|
await manager.submit(null, undefined);
|
|
3049
3117
|
},
|
|
3050
3118
|
// ── Raw LangGraph signals ─────────────────────────────────────────────
|
|
@@ -3056,7 +3124,9 @@ function agent(options) {
|
|
|
3056
3124
|
// ── Other LangGraph-specific fields ──────────────────────────────────
|
|
3057
3125
|
value: value,
|
|
3058
3126
|
hasValue: hasValueSig,
|
|
3059
|
-
reload: () =>
|
|
3127
|
+
reload: () => {
|
|
3128
|
+
void resubmitWithToolRecovery();
|
|
3129
|
+
},
|
|
3060
3130
|
toolProgress: toolProgSig,
|
|
3061
3131
|
queue: queueSig,
|
|
3062
3132
|
activeSubagents,
|