@librechat/agents 3.9.2 → 3.9.4
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/README.md +32 -0
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +11 -0
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +54 -5
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +97 -5
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/acceptedModelResponse.cjs +168 -0
- package/dist/cjs/graphs/acceptedModelResponse.cjs.map +1 -0
- package/dist/cjs/graphs/handoff.cjs +195 -0
- package/dist/cjs/graphs/handoff.cjs.map +1 -0
- package/dist/cjs/graphs/index.cjs +1 -0
- package/dist/cjs/llm/invoke.cjs +10 -5
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/streamLimits.cjs +1 -1
- package/dist/cjs/llm/streamLimits.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/messages/fading.cjs +14 -6
- package/dist/cjs/messages/fading.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +95 -36
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/openai/index.cjs +2 -0
- package/dist/cjs/openai/index.cjs.map +1 -1
- package/dist/cjs/openai/toolProjection.cjs +196 -0
- package/dist/cjs/openai/toolProjection.cjs.map +1 -0
- package/dist/cjs/run.cjs +41 -4
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1 -1
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +9 -4
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +5 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/utils/acceptedToolArguments.cjs +143 -0
- package/dist/cjs/utils/acceptedToolArguments.cjs.map +1 -0
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +11 -0
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +54 -5
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +97 -5
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/acceptedModelResponse.mjs +165 -0
- package/dist/esm/graphs/acceptedModelResponse.mjs.map +1 -0
- package/dist/esm/graphs/handoff.mjs +193 -0
- package/dist/esm/graphs/handoff.mjs.map +1 -0
- package/dist/esm/graphs/index.mjs +1 -0
- package/dist/esm/llm/invoke.mjs +10 -5
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/streamLimits.mjs +1 -1
- package/dist/esm/llm/streamLimits.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -3
- package/dist/esm/messages/fading.mjs +14 -7
- package/dist/esm/messages/fading.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +95 -37
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/openai/index.mjs +2 -1
- package/dist/esm/openai/index.mjs.map +1 -1
- package/dist/esm/openai/toolProjection.mjs +196 -0
- package/dist/esm/openai/toolProjection.mjs.map +1 -0
- package/dist/esm/run.mjs +41 -4
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1 -1
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +9 -4
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +5 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +5 -2
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/utils/acceptedToolArguments.mjs +142 -0
- package/dist/esm/utils/acceptedToolArguments.mjs.map +1 -0
- package/dist/types/common/enum.d.ts +4 -0
- package/dist/types/graphs/Graph.d.ts +5 -1
- package/dist/types/graphs/MultiAgentGraph.d.ts +3 -0
- package/dist/types/graphs/acceptedModelResponse.d.ts +15 -0
- package/dist/types/graphs/handoff.d.ts +28 -0
- package/dist/types/graphs/index.d.ts +1 -0
- package/dist/types/messages/fading.d.ts +14 -2
- package/dist/types/messages/prune.d.ts +7 -0
- package/dist/types/openai/arguments.d.ts +2 -0
- package/dist/types/openai/index.d.ts +2 -0
- package/dist/types/openai/toolProjection.d.ts +30 -0
- package/dist/types/run.d.ts +5 -0
- package/dist/types/tools/ToolNode.d.ts +2 -1
- package/dist/types/types/graph.d.ts +51 -4
- package/dist/types/types/run.d.ts +8 -1
- package/dist/types/types/stream.d.ts +22 -1
- package/dist/types/types/tools.d.ts +5 -0
- package/dist/types/utils/acceptedToolArguments.d.ts +10 -0
- package/package.json +1 -1
- package/src/common/enum.ts +5 -0
- package/src/events.ts +24 -1
- package/src/graphs/Graph.ts +103 -0
- package/src/graphs/MultiAgentGraph.ts +142 -4
- package/src/graphs/acceptedModelResponse.ts +307 -0
- package/src/graphs/handoff.ts +263 -0
- package/src/graphs/index.ts +2 -0
- package/src/llm/invoke.ts +39 -14
- package/src/llm/streamLimits.ts +1 -1
- package/src/messages/fading.ts +30 -5
- package/src/messages/prune.ts +168 -55
- package/src/openai/arguments.ts +2 -0
- package/src/openai/index.ts +6 -0
- package/src/openai/toolProjection.ts +318 -0
- package/src/run.ts +75 -2
- package/src/session/AgentSession.ts +2 -2
- package/src/stream.ts +21 -1
- package/src/tools/ToolNode.ts +19 -6
- package/src/tools/subagent/SubagentReplay.ts +10 -9
- package/src/types/graph.ts +59 -10
- package/src/types/run.ts +8 -1
- package/src/types/stream.ts +26 -0
- package/src/types/tools.ts +5 -0
- package/src/utils/acceptedToolArguments.ts +204 -0
package/src/run.ts
CHANGED
|
@@ -111,6 +111,7 @@ import { initializeLangfuseTracing } from './instrumentation';
|
|
|
111
111
|
import { seedRunInitialSessions } from '@/utils/toolSessions';
|
|
112
112
|
import { getTraceIdSeed } from '@/langfuseRuntimeContext';
|
|
113
113
|
import { resolveClientOptionsModel } from '@/llm/request';
|
|
114
|
+
import { HandoffLimitError } from '@/graphs/handoff';
|
|
114
115
|
import { createGraph } from '@/graphs/createGraph';
|
|
115
116
|
import { isFadingTier } from '@/messages/fading';
|
|
116
117
|
import { resolveMaxSeals } from '@/llm/preempt';
|
|
@@ -291,6 +292,7 @@ function getInterruptHookSessionId(payload: unknown): string | undefined {
|
|
|
291
292
|
type InterruptStateSnapshot = {
|
|
292
293
|
config?: RunnableConfig;
|
|
293
294
|
values?: {
|
|
295
|
+
handoffState?: t.HandoffState;
|
|
294
296
|
messages?: BaseMessage[];
|
|
295
297
|
runStepState?: t.RunStepResumeState;
|
|
296
298
|
};
|
|
@@ -466,6 +468,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
466
468
|
private langfuse?: t.LangfuseConfig;
|
|
467
469
|
private toolOutputReferences?: t.ToolOutputReferencesConfig;
|
|
468
470
|
private eagerEventToolExecution?: t.EagerEventToolExecutionConfig;
|
|
471
|
+
private clientDelegatedToolNames?: readonly string[];
|
|
469
472
|
private codeSessionToolNames?: string[];
|
|
470
473
|
private interruptingToolNames?: string[];
|
|
471
474
|
private toolExecution?: t.ToolExecutionConfig;
|
|
@@ -512,6 +515,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
512
515
|
/** Distinguishes sibling forks started from the same explicit checkpoint. */
|
|
513
516
|
private checkpointForkSeq = 0;
|
|
514
517
|
private _haltedReason: string | undefined;
|
|
518
|
+
private _handoffOutcome?: t.HandoffOutcome;
|
|
515
519
|
|
|
516
520
|
private constructor(config: Partial<t.RunConfig>) {
|
|
517
521
|
const runId = config.runId ?? '';
|
|
@@ -546,12 +550,19 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
546
550
|
}
|
|
547
551
|
}
|
|
548
552
|
|
|
553
|
+
if (
|
|
554
|
+
(config.clientDelegatedToolNames?.length ?? 0) > 0 &&
|
|
555
|
+
handlerRegistry.getHandler(GraphEvents.ON_MODEL_RESPONSE) == null
|
|
556
|
+
) {
|
|
557
|
+
throw new Error('Client tool delegation requires an accepted-result handler');
|
|
558
|
+
}
|
|
549
559
|
this.handlerRegistry = handlerRegistry;
|
|
550
560
|
this.hookRegistry = config.hooks;
|
|
551
561
|
this.humanInTheLoop = config.humanInTheLoop;
|
|
552
562
|
this.langfuse = config.langfuse;
|
|
553
563
|
this.toolOutputReferences = config.toolOutputReferences;
|
|
554
564
|
this.eagerEventToolExecution = config.eagerEventToolExecution;
|
|
565
|
+
this.clientDelegatedToolNames = config.clientDelegatedToolNames;
|
|
555
566
|
this.codeSessionToolNames = config.codeSessionToolNames;
|
|
556
567
|
this.interruptingToolNames = config.interruptingToolNames;
|
|
557
568
|
this.toolExecution = config.toolExecution;
|
|
@@ -570,6 +581,9 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
570
581
|
|
|
571
582
|
/** Handle different graph types */
|
|
572
583
|
if (config.graphConfig.type === 'multi-agent') {
|
|
584
|
+
if (this.clientDelegatedToolNames != null && this.clientDelegatedToolNames.length > 0) {
|
|
585
|
+
throw new Error('Client tool delegation requires a single-agent graph');
|
|
586
|
+
}
|
|
573
587
|
this.graphRunnable = this.createMultiAgentGraph(config.graphConfig);
|
|
574
588
|
if (this.Graph) {
|
|
575
589
|
this.Graph.handlerRegistry = handlerRegistry;
|
|
@@ -660,6 +674,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
660
674
|
preemption: this.preemption,
|
|
661
675
|
streamLimits: this.streamLimits,
|
|
662
676
|
toolExecution: this.toolExecution,
|
|
677
|
+
clientDelegatedToolNames: this.clientDelegatedToolNames,
|
|
663
678
|
},
|
|
664
679
|
});
|
|
665
680
|
/** Propagate compile options from graph config */
|
|
@@ -683,7 +698,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
683
698
|
private createMultiAgentGraph(
|
|
684
699
|
config: t.MultiAgentGraphConfig
|
|
685
700
|
): t.CompiledStateWorkflow {
|
|
686
|
-
const { agents, edges, compileOptions } = config;
|
|
701
|
+
const { agents, edges, compileOptions, entryAgentId, maxHandoffs } = config;
|
|
687
702
|
|
|
688
703
|
const multiAgentGraph = createGraph({
|
|
689
704
|
kind: 'multi-agent',
|
|
@@ -691,6 +706,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
691
706
|
runId: this.id,
|
|
692
707
|
agents,
|
|
693
708
|
edges,
|
|
709
|
+
entryAgentId,
|
|
710
|
+
maxHandoffs,
|
|
694
711
|
langfuse: this.langfuse,
|
|
695
712
|
tokenCounter: this.tokenCounter,
|
|
696
713
|
indexTokenCountMap: this.indexTokenCountMap,
|
|
@@ -1053,6 +1070,11 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1053
1070
|
) {
|
|
1054
1071
|
return;
|
|
1055
1072
|
}
|
|
1073
|
+
// Accepted results are graph-owned, never inferred from provider/tool callbacks.
|
|
1074
|
+
if (
|
|
1075
|
+
eventName === GraphEvents.ON_MODEL_RESPONSE ||
|
|
1076
|
+
eventName === GraphEvents.ON_MODEL_TOOLS_CLAIMED
|
|
1077
|
+
) return;
|
|
1056
1078
|
const handler = this.handlerRegistry?.getHandler(eventName);
|
|
1057
1079
|
/**
|
|
1058
1080
|
* Tool completions arriving over the custom-event channel are the only
|
|
@@ -1237,6 +1259,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1237
1259
|
}
|
|
1238
1260
|
const graphRunnable = this.graphRunnable;
|
|
1239
1261
|
const graph = this.Graph;
|
|
1262
|
+
this._handoffOutcome = undefined;
|
|
1240
1263
|
|
|
1241
1264
|
/**
|
|
1242
1265
|
* `Command` inputs (`Command({ resume, update?, goto? })`) are
|
|
@@ -1347,6 +1370,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1347
1370
|
checkpointId === '' ? 0 : ++this.checkpointForkSeq,
|
|
1348
1371
|
]);
|
|
1349
1372
|
graph.resetValues(streamOptions?.keepContent, checkpointScope);
|
|
1373
|
+
graph.handoffRouting?.start();
|
|
1350
1374
|
graph.startStopContinuationExecution(nanoid());
|
|
1351
1375
|
}
|
|
1352
1376
|
this._interrupt = undefined;
|
|
@@ -1482,9 +1506,18 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1482
1506
|
|
|
1483
1507
|
const consumeStream = async (): Promise<void> => {
|
|
1484
1508
|
let streamInputs: t.IState | Command = inputs;
|
|
1485
|
-
if (!isResume &&
|
|
1509
|
+
if (!isResume && graph.handoffRouting != null) {
|
|
1486
1510
|
streamInputs = {
|
|
1487
1511
|
...(inputs as t.IState),
|
|
1512
|
+
handoffState: graph.handoffRouting.snapshot(),
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
if (!isResume && this.hasCheckpointer) {
|
|
1516
|
+
streamInputs = {
|
|
1517
|
+
...(streamInputs as t.IState),
|
|
1518
|
+
...(graph.handoffRouting == null
|
|
1519
|
+
? {}
|
|
1520
|
+
: { handoffState: new Overwrite(graph.handoffRouting.snapshot()) }),
|
|
1488
1521
|
runStepState: new Overwrite(graph.createRunStepResumeState()),
|
|
1489
1522
|
} as unknown as t.IState;
|
|
1490
1523
|
} else if (overwriteLegacyResumeState) {
|
|
@@ -1538,6 +1571,10 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1538
1571
|
|
|
1539
1572
|
const modelEndAt =
|
|
1540
1573
|
eventName === GraphEvents.CHAT_MODEL_END ? Date.now() : undefined;
|
|
1574
|
+
if (
|
|
1575
|
+
eventName === GraphEvents.ON_MODEL_RESPONSE ||
|
|
1576
|
+
eventName === GraphEvents.ON_MODEL_TOOLS_CLAIMED
|
|
1577
|
+
) continue;
|
|
1541
1578
|
const handler = this.handlerRegistry?.getHandler(eventName);
|
|
1542
1579
|
if (handler) {
|
|
1543
1580
|
await handler.handle(eventName, data, metadata, this.Graph);
|
|
@@ -1679,6 +1716,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1679
1716
|
? injected
|
|
1680
1717
|
: [...graph.messages, ...injected],
|
|
1681
1718
|
runStepState: graph.createRunStepResumeState(),
|
|
1719
|
+
handoffState: graph.handoffRouting?.snapshot(),
|
|
1682
1720
|
};
|
|
1683
1721
|
streamConfig = completedSegmentConfig;
|
|
1684
1722
|
continue;
|
|
@@ -1716,6 +1754,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1716
1754
|
} catch (err) {
|
|
1717
1755
|
terminalAt = Date.now();
|
|
1718
1756
|
streamThrew = true;
|
|
1757
|
+
if (err instanceof HandoffLimitError)
|
|
1758
|
+
this._haltedReason = 'handoff_limit';
|
|
1719
1759
|
await langfuseHandler?.handleChainError(
|
|
1720
1760
|
err instanceof Error ? err : new Error(String(err)),
|
|
1721
1761
|
this.id
|
|
@@ -1860,6 +1900,13 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1860
1900
|
* `HumanInTheLoopConfig` JSDoc.
|
|
1861
1901
|
*/
|
|
1862
1902
|
const awaitingResume = this.isAwaitingResume(streamThrew);
|
|
1903
|
+
this._handoffOutcome = graph.handoffRouting?.outcome(
|
|
1904
|
+
this.getHandoffIncompleteReason(
|
|
1905
|
+
streamThrew,
|
|
1906
|
+
awaitingResume,
|
|
1907
|
+
config.signal
|
|
1908
|
+
)
|
|
1909
|
+
);
|
|
1863
1910
|
if (!this.skipCleanup && !awaitingResume) {
|
|
1864
1911
|
this.Graph.clearHeavyState();
|
|
1865
1912
|
}
|
|
@@ -1921,6 +1968,31 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1921
1968
|
return this._haltedReason;
|
|
1922
1969
|
}
|
|
1923
1970
|
|
|
1971
|
+
private getHandoffIncompleteReason(
|
|
1972
|
+
streamThrew: boolean,
|
|
1973
|
+
awaitingResume: boolean,
|
|
1974
|
+
signal?: AbortSignal
|
|
1975
|
+
): string | undefined {
|
|
1976
|
+
if (this._haltedReason != null) return this._haltedReason;
|
|
1977
|
+
if (signal?.aborted === true || this.Graph?.signal?.aborted === true) {
|
|
1978
|
+
return 'aborted';
|
|
1979
|
+
}
|
|
1980
|
+
if (streamThrew) return 'error';
|
|
1981
|
+
if (awaitingResume) return 'interrupted';
|
|
1982
|
+
if (this.Graph?.subagentScope === true) return 'subagent';
|
|
1983
|
+
return undefined;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/** Execution evidence only. The host must authorize and durably commit a candidate. */
|
|
1987
|
+
getHandoffOutcome(): t.HandoffOutcome | undefined {
|
|
1988
|
+
const outcome = this._handoffOutcome;
|
|
1989
|
+
if (outcome == null) return undefined;
|
|
1990
|
+
return {
|
|
1991
|
+
...outcome,
|
|
1992
|
+
transitions: outcome.transitions.map((item) => ({ ...item })),
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1924
1996
|
/**
|
|
1925
1997
|
* Resume a paused HITL run with the value the user (or whatever
|
|
1926
1998
|
* decided the interrupt) supplied. The default `TResume` covers the
|
|
@@ -2134,6 +2206,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
2134
2206
|
const snapshot = await workflow.getState(callerConfig as RunnableConfig, {
|
|
2135
2207
|
subgraphs: true,
|
|
2136
2208
|
});
|
|
2209
|
+
this.Graph?.handoffRouting?.resume(snapshot.values?.handoffState);
|
|
2137
2210
|
const persistedInterrupt = getFirstPersistedInterrupt(snapshot);
|
|
2138
2211
|
if (persistedInterrupt == null) {
|
|
2139
2212
|
return;
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
deriveSessionMessages,
|
|
28
28
|
releaseSessionProjection,
|
|
29
29
|
} from './sessionProjection';
|
|
30
|
+
import { FADING_TIER_VERSION, isFadingTier } from '@/messages/fading';
|
|
30
31
|
import { createSummarizeNode } from '@/summarization/node';
|
|
31
32
|
import { resolveStreamLimits } from '@/llm/streamLimits';
|
|
32
33
|
import { JsonlSessionStore } from './JsonlSessionStore';
|
|
@@ -34,7 +35,6 @@ import { AgentContext } from '@/agents/AgentContext';
|
|
|
34
35
|
import { ContentTypes, GraphEvents } from '@/common';
|
|
35
36
|
import { createRunId, createSessionId } from './ids';
|
|
36
37
|
import { deriveMessages } from './deriveMessages';
|
|
37
|
-
import { isFadingTier } from '@/messages/fading';
|
|
38
38
|
import { createRunHandlers } from './handlers';
|
|
39
39
|
import { Run } from '@/run';
|
|
40
40
|
|
|
@@ -420,7 +420,7 @@ function mergeFadingTier(
|
|
|
420
420
|
masked !== current.masked ||
|
|
421
421
|
masked !== incoming.masked;
|
|
422
422
|
return {
|
|
423
|
-
v:
|
|
423
|
+
v: FADING_TIER_VERSION,
|
|
424
424
|
budgetTokens,
|
|
425
425
|
masked,
|
|
426
426
|
...(latched ? { latched: true } : {}),
|
package/src/stream.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
truncateToolResultContent,
|
|
57
57
|
} from '@/utils/truncation';
|
|
58
58
|
import { resolveToolOutcome, outcomeFieldsFromResult } from '@/tools/intentArg';
|
|
59
|
+
import { snapshotValidatedModelChunk } from '@/graphs/acceptedModelResponse';
|
|
59
60
|
import { TOOL_OUTPUT_REF_PATTERN } from '@/tools/toolOutputReferences';
|
|
60
61
|
import { PreparedSubagentError } from '@/tools/preparedSubagents';
|
|
61
62
|
import { isReasoningContentBlock } from '@/messages/core';
|
|
@@ -746,6 +747,10 @@ function createEagerToolExecutionPlan(args: {
|
|
|
746
747
|
toolCall.id == null ||
|
|
747
748
|
toolCall.id === '' ||
|
|
748
749
|
toolCall.name === '' ||
|
|
750
|
+
// A serialized parsed call is not a prepared executable object;
|
|
751
|
+
// parsing is permitted only on sealed raw tool_call_chunks.
|
|
752
|
+
typeof toolCall.args === 'string' ||
|
|
753
|
+
graph.clientDelegatedToolNames?.has(toolCall.name) === true ||
|
|
749
754
|
(!skipExisting && graph.eagerEventToolExecutions.has(toolCall.id))
|
|
750
755
|
)
|
|
751
756
|
) {
|
|
@@ -794,6 +799,9 @@ function startEagerToolExecutions(args: {
|
|
|
794
799
|
skipExisting?: boolean;
|
|
795
800
|
}): void {
|
|
796
801
|
const { graph, metadata, agentContext, toolCalls, skipExisting } = args;
|
|
802
|
+
// A later call in the same model turn may be client-delegated. Do not
|
|
803
|
+
// pre-execute an earlier SDK call in a run that rejects mixed batches.
|
|
804
|
+
if ((graph.clientDelegatedToolNames?.size ?? 0) > 0) return;
|
|
797
805
|
const entries = createEagerToolExecutionPlan({
|
|
798
806
|
graph,
|
|
799
807
|
metadata,
|
|
@@ -1345,6 +1353,10 @@ function startPreparedSubagents(
|
|
|
1345
1353
|
metadata?: Record<string, unknown>
|
|
1346
1354
|
): void {
|
|
1347
1355
|
const attempt = resolveGenerationKey(metadata);
|
|
1356
|
+
if ((graph.clientDelegatedToolNames?.size ?? 0) > 0) return;
|
|
1357
|
+
// A parsed string call is not executable even if the same event carries
|
|
1358
|
+
// sealed raw fragments. Wait for a separately validated complete call.
|
|
1359
|
+
if (chunk.tool_calls?.some((call) => typeof call.args === 'string') === true) return;
|
|
1348
1360
|
if (
|
|
1349
1361
|
(graph as Partial<StandardGraph>).canPrestartSubagents?.(agentContext) !==
|
|
1350
1362
|
true ||
|
|
@@ -1389,6 +1401,9 @@ function startPreparedSubagents(
|
|
|
1389
1401
|
for (const call of calls) {
|
|
1390
1402
|
if (
|
|
1391
1403
|
call.name === Constants.SUBAGENT &&
|
|
1404
|
+
graph.clientDelegatedToolNames?.has(call.name) !== true &&
|
|
1405
|
+
typeof call.args === 'object' &&
|
|
1406
|
+
!Array.isArray(call.args) &&
|
|
1392
1407
|
!hasToolOutputReference(call.args)
|
|
1393
1408
|
) {
|
|
1394
1409
|
graph.prestartSubagent(call, attempt, agentContext);
|
|
@@ -1643,7 +1658,7 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1643
1658
|
return;
|
|
1644
1659
|
}
|
|
1645
1660
|
|
|
1646
|
-
|
|
1661
|
+
let chunk = data.chunk as Partial<AIMessageChunk>;
|
|
1647
1662
|
|
|
1648
1663
|
/** Attempts stamp their breaker epoch into event metadata; a mismatch
|
|
1649
1664
|
* marks a straggling chunk from a failed run that outlived
|
|
@@ -1681,6 +1696,10 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1681
1696
|
}
|
|
1682
1697
|
};
|
|
1683
1698
|
|
|
1699
|
+
// Callback delivery can beat the producer's iterator. Validate before
|
|
1700
|
+
// accounting, run steps, or eager dispatch reads raw tool descriptors.
|
|
1701
|
+
chunk = snapshotValidatedModelChunk(chunk as AIMessageChunk);
|
|
1702
|
+
|
|
1684
1703
|
/**
|
|
1685
1704
|
* Enforced before every content-specific early return below
|
|
1686
1705
|
* (server-tool results, deferred mixed reasoning, late OpenRouter
|
|
@@ -1885,6 +1904,7 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1885
1904
|
chunk.response_metadata as Record<string, unknown> | undefined
|
|
1886
1905
|
);
|
|
1887
1906
|
const canStreamEager =
|
|
1907
|
+
chunk.tool_calls?.some((call) => typeof call.args === 'string') !== true &&
|
|
1888
1908
|
(allowSequentialSeal || hasExplicitStreamedToolCallSeals(chunk)) &&
|
|
1889
1909
|
!hasPotentialDirectToolInStreamContext({ graph, agentContext }) &&
|
|
1890
1910
|
isEagerToolExecutionEnabledForBatch({ graph, metadata, agentContext });
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -974,8 +974,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
974
974
|
* other's in-flight state.
|
|
975
975
|
*/
|
|
976
976
|
private anonBatchCounter: number = 0;
|
|
977
|
+
private handoffRouting?: t.ToolNodeOptions['handoffRouting'];
|
|
977
978
|
|
|
978
979
|
constructor({
|
|
980
|
+
handoffRouting,
|
|
979
981
|
tools,
|
|
980
982
|
toolMap,
|
|
981
983
|
name,
|
|
@@ -1019,6 +1021,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1019
1021
|
preparedSubagents,
|
|
1020
1022
|
restoreRunStepResumeState,
|
|
1021
1023
|
createRunStepResumeState,
|
|
1024
|
+
onToolCallsClaimed,
|
|
1022
1025
|
}: t.ToolNodeConstructorParams) {
|
|
1023
1026
|
super({
|
|
1024
1027
|
name: name ?? TOOL_NODE_RUN_NAME,
|
|
@@ -1153,6 +1156,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1153
1156
|
}
|
|
1154
1157
|
const state = input as T & Pick<t.BaseGraphState, 'runStepState'>;
|
|
1155
1158
|
restoreRunStepResumeState?.(state.runStepState, config);
|
|
1159
|
+
// Freeze replay authority before observers can yield to sibling tasks.
|
|
1160
|
+
if (onToolCallsClaimed != null && assistantBatch?.message.id != null) {
|
|
1161
|
+
await onToolCallsClaimed(assistantBatch.message.id, config);
|
|
1162
|
+
}
|
|
1156
1163
|
let result: T;
|
|
1157
1164
|
try {
|
|
1158
1165
|
result = await this.run(input, config, referenceReplay);
|
|
@@ -1219,6 +1226,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1219
1226
|
this.runLangfuse = runLangfuse;
|
|
1220
1227
|
this.agentLangfuse = agentLangfuse;
|
|
1221
1228
|
this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
1229
|
+
this.handoffRouting = handoffRouting;
|
|
1222
1230
|
this.toolCallStepIds = toolCallStepIds;
|
|
1223
1231
|
this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
|
|
1224
1232
|
this.loadRuntimeTools = loadRuntimeTools;
|
|
@@ -3221,10 +3229,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3221
3229
|
|
|
3222
3230
|
private retainCodeSessionInputsFromRequests(
|
|
3223
3231
|
requests: Iterable<t.ToolCallRequest>,
|
|
3224
|
-
baselineByRequestId: ReadonlyMap<
|
|
3225
|
-
string,
|
|
3226
|
-
ReadonlyMap<string, string>
|
|
3227
|
-
>
|
|
3232
|
+
baselineByRequestId: ReadonlyMap<string, ReadonlyMap<string, string>>
|
|
3228
3233
|
): void {
|
|
3229
3234
|
if (!this.sessions) {
|
|
3230
3235
|
return;
|
|
@@ -5140,8 +5145,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
5140
5145
|
call.id == null
|
|
5141
5146
|
? undefined
|
|
5142
5147
|
: baseContext.resolvedArgsByCallId?.get(call.id);
|
|
5143
|
-
const codeSessionBaseline =
|
|
5144
|
-
|
|
5148
|
+
const codeSessionBaseline = baseContext.codeSessionBaselineByCallId?.get(
|
|
5149
|
+
call.id ?? ''
|
|
5150
|
+
);
|
|
5145
5151
|
const result: SettledDirectToolResult = {
|
|
5146
5152
|
proposal: structuredClone({
|
|
5147
5153
|
name: call.name,
|
|
@@ -6073,6 +6079,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
6073
6079
|
if (replayBatchKey != null) {
|
|
6074
6080
|
this.settledDirectResultsByBatch.delete(replayBatchKey);
|
|
6075
6081
|
}
|
|
6082
|
+
if (!Array.isArray(input) && !this.isSendInput(input)) {
|
|
6083
|
+
this.handoffRouting?.finalize(
|
|
6084
|
+
combinedOutputs.filter(isCommand),
|
|
6085
|
+
input as t.BaseGraphState,
|
|
6086
|
+
config
|
|
6087
|
+
);
|
|
6088
|
+
}
|
|
6076
6089
|
return combinedOutputs as T;
|
|
6077
6090
|
}
|
|
6078
6091
|
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
RunStepResumeState,
|
|
9
9
|
ToolSessionContext,
|
|
10
10
|
} from '@/types';
|
|
11
|
-
import { isFadingTier } from '@/messages/fading';
|
|
11
|
+
import { isFadingTier, isLegacyFadingTier } from '@/messages/fading';
|
|
12
12
|
import {
|
|
13
13
|
attachRunStepResumeState,
|
|
14
14
|
getRunStepResumeState,
|
|
@@ -290,6 +290,11 @@ export function isToolOutputReferenceState(
|
|
|
290
290
|
);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/** A current tier, or a legacy one the run then drops so it re-derives. */
|
|
294
|
+
function isStoredFadingTier(value: unknown): boolean {
|
|
295
|
+
return isFadingTier(value) || isLegacyFadingTier(value);
|
|
296
|
+
}
|
|
297
|
+
|
|
293
298
|
function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
|
|
294
299
|
if (value == null || typeof value !== 'object') {
|
|
295
300
|
return false;
|
|
@@ -306,15 +311,14 @@ function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
|
|
|
306
311
|
!state.eagerToolUsage.every(isEagerToolUsageState) ||
|
|
307
312
|
!Array.isArray(state.eagerToolSuppressions) ||
|
|
308
313
|
!state.eagerToolSuppressions.every(isString) ||
|
|
309
|
-
(state.runStepState != null &&
|
|
310
|
-
!isRunStepResumeState(state.runStepState)) ||
|
|
314
|
+
(state.runStepState != null && !isRunStepResumeState(state.runStepState)) ||
|
|
311
315
|
(state.toolOutputReferences != null &&
|
|
312
316
|
!isToolOutputReferenceState(state.toolOutputReferences)) ||
|
|
313
|
-
(state.fadingTier != null && !
|
|
317
|
+
(state.fadingTier != null && !isStoredFadingTier(state.fadingTier)) ||
|
|
314
318
|
(state.fadingTiers != null &&
|
|
315
319
|
(typeof state.fadingTiers !== 'object' ||
|
|
316
320
|
Array.isArray(state.fadingTiers) ||
|
|
317
|
-
!Object.values(state.fadingTiers).every(
|
|
321
|
+
!Object.values(state.fadingTiers).every(isStoredFadingTier)))
|
|
318
322
|
) {
|
|
319
323
|
return false;
|
|
320
324
|
}
|
|
@@ -515,10 +519,7 @@ export function attachSubagentResumeManifest(
|
|
|
515
519
|
const runStepState = getRunStepResumeState(payload);
|
|
516
520
|
if (runStepState != null) {
|
|
517
521
|
return attachRunStepResumeState(
|
|
518
|
-
attachSubagentResumeManifest(
|
|
519
|
-
stripRunStepResumeState(payload),
|
|
520
|
-
manifest
|
|
521
|
-
),
|
|
522
|
+
attachSubagentResumeManifest(stripRunStepResumeState(payload), manifest),
|
|
522
523
|
runStepState
|
|
523
524
|
);
|
|
524
525
|
}
|
package/src/types/graph.ts
CHANGED
|
@@ -10,22 +10,24 @@ import type { START, StateGraph, StateGraphArgs } from '@langchain/langgraph';
|
|
|
10
10
|
import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
11
11
|
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
12
12
|
import type { GoogleAIToolType } from '@langchain/google-common';
|
|
13
|
-
import type {
|
|
14
|
-
SummarizationNodeInput,
|
|
15
|
-
SummarizeCompleteEvent,
|
|
16
|
-
CompactionSemanticIndex,
|
|
17
|
-
SummarizationConfig,
|
|
18
|
-
SummarizeStartEvent,
|
|
19
|
-
SummarizeDeltaEvent,
|
|
20
|
-
} from '@/types/summarize';
|
|
21
13
|
import type {
|
|
22
14
|
RunStep,
|
|
15
|
+
ModelResponseEvent,
|
|
16
|
+
ModelToolsClaimedEvent,
|
|
23
17
|
RunStepDeltaEvent,
|
|
24
18
|
RunStepResumeState,
|
|
25
19
|
RunStepClosedEvent,
|
|
26
20
|
MessageDeltaEvent,
|
|
27
21
|
ReasoningDeltaEvent,
|
|
28
22
|
} from '@/types/stream';
|
|
23
|
+
import type {
|
|
24
|
+
SummarizationNodeInput,
|
|
25
|
+
SummarizeCompleteEvent,
|
|
26
|
+
CompactionSemanticIndex,
|
|
27
|
+
SummarizationConfig,
|
|
28
|
+
SummarizeStartEvent,
|
|
29
|
+
SummarizeDeltaEvent,
|
|
30
|
+
} from '@/types/summarize';
|
|
29
31
|
import type {
|
|
30
32
|
ToolMap,
|
|
31
33
|
ToolSessionMap,
|
|
@@ -78,9 +80,45 @@ export type SystemCallbacks = {
|
|
|
78
80
|
: never;
|
|
79
81
|
};
|
|
80
82
|
|
|
83
|
+
/** An executed, SDK-generated handoff, identified independently of stream events. */
|
|
84
|
+
export interface HandoffTransition {
|
|
85
|
+
id: string;
|
|
86
|
+
sourceAgentId: string;
|
|
87
|
+
targetAgentId: string;
|
|
88
|
+
toolCallId: string;
|
|
89
|
+
scope: 'turn' | 'conversation';
|
|
90
|
+
/** Causal order: number of handoffs visible at this batch's input. */
|
|
91
|
+
depth: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Versioned graph checkpoint payload, scoped to one logical user turn. */
|
|
95
|
+
export interface HandoffState {
|
|
96
|
+
version: 1;
|
|
97
|
+
executionId: string;
|
|
98
|
+
entryAgentId: string;
|
|
99
|
+
maxHandoffs?: number;
|
|
100
|
+
transitions: HandoffTransition[];
|
|
101
|
+
parallel: boolean;
|
|
102
|
+
/** False when resuming an older checkpoint without complete routing provenance. */
|
|
103
|
+
historyComplete?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Only a candidate from a completed top-level run may be promoted by a host. */
|
|
107
|
+
export type HandoffOutcome = {
|
|
108
|
+
executionId: string;
|
|
109
|
+
entryAgentId: string;
|
|
110
|
+
transitions: readonly HandoffTransition[];
|
|
111
|
+
} & (
|
|
112
|
+
| { status: 'candidate'; agentId: string; transitionId: string }
|
|
113
|
+
| { status: 'unchanged' | 'ambiguous' }
|
|
114
|
+
| { status: 'incomplete'; reason: string }
|
|
115
|
+
);
|
|
116
|
+
|
|
81
117
|
export type BaseGraphState = {
|
|
82
118
|
messages: BaseMessage[];
|
|
83
119
|
runStepState?: RunStepResumeState;
|
|
120
|
+
/** SDK-owned routing state; hosts must not synthesize it from messages. */
|
|
121
|
+
handoffState?: HandoffState;
|
|
84
122
|
/**
|
|
85
123
|
* The summary a summarize-only run produced. Kept in state because such a
|
|
86
124
|
* run has no assistant reply: trace roots report it as the run's output
|
|
@@ -147,7 +185,7 @@ export interface ContextUsageEvent {
|
|
|
147
185
|
* `RunConfig.fadingTiers[agentId]`.
|
|
148
186
|
*/
|
|
149
187
|
export interface FadingTier {
|
|
150
|
-
v:
|
|
188
|
+
v: 2;
|
|
151
189
|
/** Token budget the caps derive from, in raw token space. Never grows;
|
|
152
190
|
* clamped to the current context window when seeded. */
|
|
153
191
|
budgetTokens: number;
|
|
@@ -166,6 +204,8 @@ export interface EventHandler {
|
|
|
166
204
|
data:
|
|
167
205
|
| StreamEventData
|
|
168
206
|
| ModelEndData
|
|
207
|
+
| ModelResponseEvent
|
|
208
|
+
| ModelToolsClaimedEvent
|
|
169
209
|
| RunStep
|
|
170
210
|
| RunStepDeltaEvent
|
|
171
211
|
| RunStepClosedEvent
|
|
@@ -370,6 +410,8 @@ export type StandardGraphInput = {
|
|
|
370
410
|
agents: AgentInputs[];
|
|
371
411
|
/** Execution backend used to resolve the effective tool registry. */
|
|
372
412
|
toolExecution?: ToolExecutionConfig;
|
|
413
|
+
/** Trusted single-agent client delegation policy; mixed batches fail closed. */
|
|
414
|
+
clientDelegatedToolNames?: readonly string[];
|
|
373
415
|
langfuse?: LangfuseConfig;
|
|
374
416
|
tokenCounter?: TokenCounter;
|
|
375
417
|
indexTokenCountMap?: Record<string, number>;
|
|
@@ -434,6 +476,8 @@ export type GraphEdge = {
|
|
|
434
476
|
condition?: (state: BaseGraphState) => boolean | string | string[];
|
|
435
477
|
/** 'handoff' creates tools for dynamic routing, 'direct' creates direct edges, which also allow parallel execution */
|
|
436
478
|
edgeType?: 'handoff' | 'direct';
|
|
479
|
+
/** Host may promote the destination after successful top-level completion. */
|
|
480
|
+
handoffScope?: 'turn' | 'conversation';
|
|
437
481
|
/**
|
|
438
482
|
* For direct edges: Optional prompt to add when transitioning through this edge.
|
|
439
483
|
* String prompts can include variables like {results} which will be replaced with
|
|
@@ -463,15 +507,20 @@ export type GraphEdge = {
|
|
|
463
507
|
|
|
464
508
|
export type GraphSubagentEdge = Omit<
|
|
465
509
|
GraphEdge,
|
|
466
|
-
'edgeType' | 'condition' | 'promptKey'
|
|
510
|
+
'edgeType' | 'condition' | 'promptKey' | 'handoffScope'
|
|
467
511
|
> & {
|
|
468
512
|
edgeType: 'direct';
|
|
469
513
|
condition?: never;
|
|
470
514
|
promptKey?: never;
|
|
515
|
+
handoffScope?: never;
|
|
471
516
|
};
|
|
472
517
|
|
|
473
518
|
export type MultiAgentGraphInput = StandardGraphInput & {
|
|
474
519
|
edges: GraphEdge[];
|
|
520
|
+
/** Explicit fresh-turn entry; absent preserves topology-inferred entry points. */
|
|
521
|
+
entryAgentId?: string;
|
|
522
|
+
/** Shared logical-turn handoff cap. Zero forbids handoffs; absent uses only recursion limits. */
|
|
523
|
+
maxHandoffs?: number;
|
|
475
524
|
/** Captures the designated member's final AI turn in graph state. */
|
|
476
525
|
resultAgentId?: string;
|
|
477
526
|
/** Optional per-member Pregel budget when the outer graph has its own topology budget. */
|
package/src/types/run.ts
CHANGED
|
@@ -115,11 +115,13 @@ export type MultiAgentGraphConfig = {
|
|
|
115
115
|
compileOptions?: g.CompileOptions;
|
|
116
116
|
agents: g.AgentInputs[];
|
|
117
117
|
edges: g.GraphEdge[];
|
|
118
|
+
entryAgentId?: string;
|
|
119
|
+
maxHandoffs?: number;
|
|
118
120
|
};
|
|
119
121
|
|
|
120
122
|
export type StandardGraphConfig = Omit<
|
|
121
123
|
MultiAgentGraphConfig,
|
|
122
|
-
'edges' | 'type'
|
|
124
|
+
'edges' | 'type' | 'entryAgentId' | 'maxHandoffs'
|
|
123
125
|
> & { type?: 'standard'; signal?: AbortSignal };
|
|
124
126
|
|
|
125
127
|
/**
|
|
@@ -277,6 +279,11 @@ export type RunConfig = {
|
|
|
277
279
|
*/
|
|
278
280
|
langfuse?: g.LangfuseConfig;
|
|
279
281
|
customHandlers?: Record<string, g.EventHandler>;
|
|
282
|
+
/** Explicit client-owned tools for a single-agent graph. A batch mixing
|
|
283
|
+
* client and SDK/provider calls fails closed; omitted means SDK ownership.
|
|
284
|
+
* Hosts must register the corresponding model-facing tool schemas.
|
|
285
|
+
*/
|
|
286
|
+
clientDelegatedToolNames?: readonly string[];
|
|
280
287
|
/**
|
|
281
288
|
* Receives token usage for every model call made inside subagent child
|
|
282
289
|
* runs (including nested subagents). Child graphs execute via `invoke()`
|
package/src/types/stream.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/types/stream.ts
|
|
2
2
|
import type {
|
|
3
|
+
AIMessageChunk,
|
|
3
4
|
MessageContentImageUrl,
|
|
4
5
|
MessageContentText,
|
|
5
6
|
ToolMessage,
|
|
@@ -15,6 +16,24 @@ import type { SummarizeCompleteEvent } from '@/types/summarize';
|
|
|
15
16
|
import type { ToolEndEvent } from '@/types/tools';
|
|
16
17
|
import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
|
|
17
18
|
|
|
19
|
+
/** One accepted model result, detached from execution state before host dispatch.
|
|
20
|
+
* Provider chunks, failed attempts and UI run-step events are not this contract. */
|
|
21
|
+
export interface ModelResponseEvent {
|
|
22
|
+
type: 'model_response';
|
|
23
|
+
/** Graph-generated acceptance ID, not a provider ID or run-step index. */
|
|
24
|
+
id: string;
|
|
25
|
+
agentId: string;
|
|
26
|
+
/** Graph-state message identity used to correlate ToolNode ownership. */
|
|
27
|
+
messageId?: string;
|
|
28
|
+
toolCalls: ReadonlyArray<ToolCall>;
|
|
29
|
+
/** Same index as toolCalls. Only a trusted graph decision of 'client'
|
|
30
|
+
* permits this call on the OpenAI client wire; absence fails closed. */
|
|
31
|
+
toolCallDispositions: ReadonlyArray<'sdk' | 'provider' | 'client'>;
|
|
32
|
+
invalidToolCalls: ReadonlyArray<
|
|
33
|
+
NonNullable<AIMessageChunk['invalid_tool_calls']>[number]
|
|
34
|
+
>;
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
export type HandleLLMEnd = (
|
|
19
38
|
output: LLMResult,
|
|
20
39
|
runId: string,
|
|
@@ -542,3 +561,10 @@ export type ContentAggregatorResult = {
|
|
|
542
561
|
contentParts: Array<MessageContentComplex | undefined>;
|
|
543
562
|
aggregateContent: ContentAggregator;
|
|
544
563
|
};
|
|
564
|
+
|
|
565
|
+
/** Ownership, not successful completion. Interrupted/failed batches remain graph-owned. */
|
|
566
|
+
export interface ModelToolsClaimedEvent {
|
|
567
|
+
type: 'model_tools_claimed';
|
|
568
|
+
agentId: string;
|
|
569
|
+
messageId: string;
|
|
570
|
+
}
|
package/src/types/tools.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
|
14
14
|
import type { LangfuseConfig, SubagentExecutionContext } from './graph';
|
|
15
15
|
import type { PreparedSubagents } from '@/tools/preparedSubagents';
|
|
16
16
|
import type { RunBreakerScope } from '@/llm/streamLimits';
|
|
17
|
+
import type { HandoffRouting } from '@/graphs/handoff';
|
|
17
18
|
import type { HumanInTheLoopConfig } from './hitl';
|
|
18
19
|
import type { HookRegistry } from '@/hooks';
|
|
19
20
|
|
|
@@ -117,6 +118,8 @@ export type EagerEventToolCallChunkState = {
|
|
|
117
118
|
};
|
|
118
119
|
|
|
119
120
|
export type ToolNodeOptions = {
|
|
121
|
+
/** @internal Admission shared by tool nodes belonging to one multi-agent graph. */
|
|
122
|
+
handoffRouting?: Pick<HandoffRouting, 'finalize'>;
|
|
120
123
|
name?: string;
|
|
121
124
|
tags?: string[];
|
|
122
125
|
/** Enables LangChain/LangGraph tracing for this ToolNode. Defaults to false. */
|
|
@@ -310,6 +313,8 @@ export type ToolNodeOptions = {
|
|
|
310
313
|
) => void;
|
|
311
314
|
/** SDK-owned checkpoint snapshot for open run-step lifecycle state. */
|
|
312
315
|
createRunStepResumeState?: () => RunStepResumeState;
|
|
316
|
+
/** Internal ownership bridge, awaited before ToolNode can execute a batch. */
|
|
317
|
+
onToolCallsClaimed?: (messageId: string, config: RunnableConfig) => Promise<void>;
|
|
313
318
|
};
|
|
314
319
|
|
|
315
320
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|