@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
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { FadingTier } from '@/types/graph';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Version 2: exchange width counts only the current turn. Version 1 tiers may
|
|
4
|
+
* have latched on history whose steps storage had merged into one assistant
|
|
5
|
+
* message, so they are discarded and re-derived once.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FADING_TIER_VERSION = 2;
|
|
3
8
|
/** Context pressure at which observation masking activates. */
|
|
4
9
|
export declare const PRESSURE_THRESHOLD_MASKING = 0.8;
|
|
5
10
|
/** Smallest token budget the ladder can shrink to; keeps the emergency floor near 200 chars. */
|
|
@@ -12,7 +17,7 @@ export type FadingSignals = {
|
|
|
12
17
|
/** (pruningBudget − instruction tokens) ÷ calibrationRatio, in raw token space. */
|
|
13
18
|
effectiveRawTokens: number;
|
|
14
19
|
summarizationEnabled: boolean;
|
|
15
|
-
/** Largest number of parallel calls
|
|
20
|
+
/** Largest number of parallel calls in one assistant exchange of the current turn. */
|
|
16
21
|
toolExchangeWidth?: number;
|
|
17
22
|
/** Recovery paths force at least this rung on the current window's ladder. */
|
|
18
23
|
minRung?: number;
|
|
@@ -29,6 +34,13 @@ export type FadingCaps = {
|
|
|
29
34
|
/** Tier for a conversation that has never faded: the whole window, nothing masked. */
|
|
30
35
|
export declare function createFadingTier(window: number): FadingTier;
|
|
31
36
|
export declare function isFadingTier(value: unknown): value is FadingTier;
|
|
37
|
+
/**
|
|
38
|
+
* A well-formed tier persisted before version 2. Stored snapshots that carry
|
|
39
|
+
* one (resume manifests, session files) stay valid; every consumer then drops
|
|
40
|
+
* it through `isFadingTier`, so the tier re-derives once instead of the
|
|
41
|
+
* snapshot being rejected as corrupt.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isLegacyFadingTier(value: unknown): boolean;
|
|
32
44
|
/** Deepest rung for a window: the point where the budget reaches its floor. */
|
|
33
45
|
export declare function maxFadingRung(window: number): number;
|
|
34
46
|
/** Token budget at a rung: the window halved per rung, never below the floor. */
|
|
@@ -250,6 +250,13 @@ export declare function preFlightTruncateToolResults(params: {
|
|
|
250
250
|
indexTokenCountMap: Record<string, number | undefined>;
|
|
251
251
|
tokenCounter: TokenCounter;
|
|
252
252
|
}): number;
|
|
253
|
+
/**
|
|
254
|
+
* Leads every shortened tool-call input. Fading only shortens calls already in
|
|
255
|
+
* history, which ran with their full input; a model that reads a shortened copy
|
|
256
|
+
* as a call that was cut off re-issues it, and a side-effecting tool (sending an
|
|
257
|
+
* email) then runs again. The note says what actually happened.
|
|
258
|
+
*/
|
|
259
|
+
export declare const TOOL_INPUT_ELISION_NOTE = "Completed call; input shortened to save context.";
|
|
253
260
|
/**
|
|
254
261
|
* Serializes one structured tool-call input as valid, bounded JSON without
|
|
255
262
|
* invoking user-defined accessors or `toJSON`.
|
|
@@ -73,3 +73,5 @@ export declare function createChatCompletionUsageChunk(context: OpenAIResponseCo
|
|
|
73
73
|
export declare function writeOpenAISSE(writer: OpenAICompatibleWriter, data: OpenAIChatCompletionChunk | '[DONE]'): Promise<void>;
|
|
74
74
|
export declare function createOpenAIHandlers(config: OpenAIHandlerConfig): Record<string, t.EventHandler>;
|
|
75
75
|
export declare function sendOpenAIFinalChunk(config: OpenAIHandlerConfig, finishReason?: OpenAIChatCompletionChunkChoice['finish_reason']): Promise<void>;
|
|
76
|
+
export { createOpenAIToolCallStream } from './toolProjection';
|
|
77
|
+
export type { OpenAIToolCallStream, OpenAIToolCallStreamConfig, } from './toolProjection';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { OpenAIChatCompletionChunkChoice, OpenAIToolCall, OpenAIStreamTracker } from './index';
|
|
2
|
+
import type { EventHandler } from '@/types';
|
|
3
|
+
interface OpenAIToolCallStreamOptions {
|
|
4
|
+
/** Synchronous framing only. Async transport/backpressure is a separate boundary. */
|
|
5
|
+
emit?: (delta: OpenAIChatCompletionChunkChoice['delta']) => void;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
/** Bound retained complete-before-publish output, not model execution. Default: 1024. */
|
|
8
|
+
maxToolCalls?: number;
|
|
9
|
+
/** UTF-8 bytes of serialized arguments, names and IDs retained across accepted responses. Default: 4 MiB. */
|
|
10
|
+
maxBufferedBytes?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Streaming hosts share the finalizer's tracker; map-only hosts collect JSON output. */
|
|
13
|
+
export type OpenAIToolCallStreamConfig = OpenAIToolCallStreamOptions & ({
|
|
14
|
+
tracker: OpenAIStreamTracker;
|
|
15
|
+
toolCalls?: never;
|
|
16
|
+
emit: NonNullable<OpenAIToolCallStreamOptions['emit']>;
|
|
17
|
+
} | {
|
|
18
|
+
toolCalls: Map<number, OpenAIToolCall>;
|
|
19
|
+
tracker?: never;
|
|
20
|
+
});
|
|
21
|
+
export interface OpenAIToolCallStream {
|
|
22
|
+
/** Pass directly to Run.create({ customHandlers: stream.handlers }). */
|
|
23
|
+
handlers: Record<string, EventHandler>;
|
|
24
|
+
/** Publish only after the host verifies that the entire run completed successfully. */
|
|
25
|
+
finish: () => void;
|
|
26
|
+
abort: () => void;
|
|
27
|
+
}
|
|
28
|
+
/** Serializes finalized, accepted tool calls. No provider fragments or attempt inference. */
|
|
29
|
+
export declare function createOpenAIToolCallStream(config: OpenAIToolCallStreamConfig): OpenAIToolCallStream;
|
|
30
|
+
export {};
|
package/dist/types/run.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
14
14
|
private langfuse?;
|
|
15
15
|
private toolOutputReferences?;
|
|
16
16
|
private eagerEventToolExecution?;
|
|
17
|
+
private clientDelegatedToolNames?;
|
|
17
18
|
private codeSessionToolNames?;
|
|
18
19
|
private interruptingToolNames?;
|
|
19
20
|
private toolExecution?;
|
|
@@ -60,6 +61,7 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
60
61
|
/** Distinguishes sibling forks started from the same explicit checkpoint. */
|
|
61
62
|
private checkpointForkSeq;
|
|
62
63
|
private _haltedReason;
|
|
64
|
+
private _handoffOutcome?;
|
|
63
65
|
private constructor();
|
|
64
66
|
private createLegacyGraph;
|
|
65
67
|
private createMultiAgentGraph;
|
|
@@ -216,6 +218,9 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
216
218
|
* no halt reason.
|
|
217
219
|
*/
|
|
218
220
|
getHaltReason(): string | undefined;
|
|
221
|
+
private getHandoffIncompleteReason;
|
|
222
|
+
/** Execution evidence only. The host must authorize and durably commit a candidate. */
|
|
223
|
+
getHandoffOutcome(): t.HandoffOutcome | undefined;
|
|
219
224
|
/**
|
|
220
225
|
* Resume a paused HITL run with the value the user (or whatever
|
|
221
226
|
* decided the interrupt) supplied. The default `TResume` covers the
|
|
@@ -228,7 +228,8 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
228
228
|
* other's in-flight state.
|
|
229
229
|
*/
|
|
230
230
|
private anonBatchCounter;
|
|
231
|
-
|
|
231
|
+
private handoffRouting?;
|
|
232
|
+
constructor({ handoffRouting, tools, toolMap, name, tags, trace, runLangfuse, agentLangfuse, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, toolDefinitions, getDiscoveredToolNames, sessions, codeSessionKey, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, eagerEventToolSuppressions, agentId, executingAgentId, executionContext, executingAgentName, rootAgentId, rootAgentName, directToolNames, interruptingToolNames, codeSessionToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer, getBreakerSignal, getRunScope, preparedSubagents, restoreRunStepResumeState, createRunStepResumeState, onToolCallsClaimed, }: t.ToolNodeConstructorParams);
|
|
232
233
|
invoke(input: any, options?: Partial<RunnableConfig>): Promise<any>;
|
|
233
234
|
private withToolScope;
|
|
234
235
|
/**
|
|
@@ -4,8 +4,8 @@ import type { START, StateGraph, StateGraphArgs } from '@langchain/langgraph';
|
|
|
4
4
|
import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
5
5
|
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
6
6
|
import type { GoogleAIToolType } from '@langchain/google-common';
|
|
7
|
+
import type { RunStep, ModelResponseEvent, ModelToolsClaimedEvent, RunStepDeltaEvent, RunStepResumeState, RunStepClosedEvent, MessageDeltaEvent, ReasoningDeltaEvent } from '@/types/stream';
|
|
7
8
|
import type { SummarizationNodeInput, SummarizeCompleteEvent, CompactionSemanticIndex, SummarizationConfig, SummarizeStartEvent, SummarizeDeltaEvent } from '@/types/summarize';
|
|
8
|
-
import type { RunStep, RunStepDeltaEvent, RunStepResumeState, RunStepClosedEvent, MessageDeltaEvent, ReasoningDeltaEvent } from '@/types/stream';
|
|
9
9
|
import type { ToolMap, ToolSessionMap, ToolEndEvent, GenericTool, LCTool, ToolExecutionConfig, ToolExecuteBatchRequest } from '@/types/tools';
|
|
10
10
|
import type { TokenCounter, StreamLimits, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
|
|
11
11
|
import type { SubagentTaskConfig } from '@/types/subagentTasks';
|
|
@@ -27,9 +27,47 @@ export type ClientCallbacks = {
|
|
|
27
27
|
export type SystemCallbacks = {
|
|
28
28
|
[K in keyof ClientCallbacks]: ClientCallbacks[K] extends ClientCallback<infer Args> ? (...args: Args) => void : never;
|
|
29
29
|
};
|
|
30
|
+
/** An executed, SDK-generated handoff, identified independently of stream events. */
|
|
31
|
+
export interface HandoffTransition {
|
|
32
|
+
id: string;
|
|
33
|
+
sourceAgentId: string;
|
|
34
|
+
targetAgentId: string;
|
|
35
|
+
toolCallId: string;
|
|
36
|
+
scope: 'turn' | 'conversation';
|
|
37
|
+
/** Causal order: number of handoffs visible at this batch's input. */
|
|
38
|
+
depth: number;
|
|
39
|
+
}
|
|
40
|
+
/** Versioned graph checkpoint payload, scoped to one logical user turn. */
|
|
41
|
+
export interface HandoffState {
|
|
42
|
+
version: 1;
|
|
43
|
+
executionId: string;
|
|
44
|
+
entryAgentId: string;
|
|
45
|
+
maxHandoffs?: number;
|
|
46
|
+
transitions: HandoffTransition[];
|
|
47
|
+
parallel: boolean;
|
|
48
|
+
/** False when resuming an older checkpoint without complete routing provenance. */
|
|
49
|
+
historyComplete?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Only a candidate from a completed top-level run may be promoted by a host. */
|
|
52
|
+
export type HandoffOutcome = {
|
|
53
|
+
executionId: string;
|
|
54
|
+
entryAgentId: string;
|
|
55
|
+
transitions: readonly HandoffTransition[];
|
|
56
|
+
} & ({
|
|
57
|
+
status: 'candidate';
|
|
58
|
+
agentId: string;
|
|
59
|
+
transitionId: string;
|
|
60
|
+
} | {
|
|
61
|
+
status: 'unchanged' | 'ambiguous';
|
|
62
|
+
} | {
|
|
63
|
+
status: 'incomplete';
|
|
64
|
+
reason: string;
|
|
65
|
+
});
|
|
30
66
|
export type BaseGraphState = {
|
|
31
67
|
messages: BaseMessage[];
|
|
32
68
|
runStepState?: RunStepResumeState;
|
|
69
|
+
/** SDK-owned routing state; hosts must not synthesize it from messages. */
|
|
70
|
+
handoffState?: HandoffState;
|
|
33
71
|
/**
|
|
34
72
|
* The summary a summarize-only run produced. Kept in state because such a
|
|
35
73
|
* run has no assistant reply: trace roots report it as the run's output
|
|
@@ -89,7 +127,7 @@ export interface ContextUsageEvent {
|
|
|
89
127
|
* `RunConfig.fadingTiers[agentId]`.
|
|
90
128
|
*/
|
|
91
129
|
export interface FadingTier {
|
|
92
|
-
v:
|
|
130
|
+
v: 2;
|
|
93
131
|
/** Token budget the caps derive from, in raw token space. Never grows;
|
|
94
132
|
* clamped to the current context window when seeded. */
|
|
95
133
|
budgetTokens: number;
|
|
@@ -101,7 +139,7 @@ export interface FadingTier {
|
|
|
101
139
|
/** Latched fading tiers keyed by agent ID. */
|
|
102
140
|
export type FadingTiers = Record<string, FadingTier>;
|
|
103
141
|
export interface EventHandler {
|
|
104
|
-
handle(event: string, data: StreamEventData | ModelEndData | RunStep | RunStepDeltaEvent | RunStepClosedEvent | MessageDeltaEvent | ReasoningDeltaEvent | SummarizeStartEvent | SummarizeDeltaEvent | SummarizeCompleteEvent | SubagentUpdateEvent | AgentLogEvent | ContextUsageEvent | ToolExecuteBatchRequest | {
|
|
142
|
+
handle(event: string, data: StreamEventData | ModelEndData | ModelResponseEvent | ModelToolsClaimedEvent | RunStep | RunStepDeltaEvent | RunStepClosedEvent | MessageDeltaEvent | ReasoningDeltaEvent | SummarizeStartEvent | SummarizeDeltaEvent | SummarizeCompleteEvent | SubagentUpdateEvent | AgentLogEvent | ContextUsageEvent | ToolExecuteBatchRequest | {
|
|
105
143
|
result: ToolEndEvent;
|
|
106
144
|
}, metadata?: Record<string, unknown>, graph?: StandardGraph | MultiAgentGraph): void | Promise<void>;
|
|
107
145
|
}
|
|
@@ -253,6 +291,8 @@ export type StandardGraphInput = {
|
|
|
253
291
|
agents: AgentInputs[];
|
|
254
292
|
/** Execution backend used to resolve the effective tool registry. */
|
|
255
293
|
toolExecution?: ToolExecutionConfig;
|
|
294
|
+
/** Trusted single-agent client delegation policy; mixed batches fail closed. */
|
|
295
|
+
clientDelegatedToolNames?: readonly string[];
|
|
256
296
|
langfuse?: LangfuseConfig;
|
|
257
297
|
tokenCounter?: TokenCounter;
|
|
258
298
|
indexTokenCountMap?: Record<string, number>;
|
|
@@ -316,6 +356,8 @@ export type GraphEdge = {
|
|
|
316
356
|
condition?: (state: BaseGraphState) => boolean | string | string[];
|
|
317
357
|
/** 'handoff' creates tools for dynamic routing, 'direct' creates direct edges, which also allow parallel execution */
|
|
318
358
|
edgeType?: 'handoff' | 'direct';
|
|
359
|
+
/** Host may promote the destination after successful top-level completion. */
|
|
360
|
+
handoffScope?: 'turn' | 'conversation';
|
|
319
361
|
/**
|
|
320
362
|
* For direct edges: Optional prompt to add when transitioning through this edge.
|
|
321
363
|
* String prompts can include variables like {results} which will be replaced with
|
|
@@ -337,13 +379,18 @@ export type GraphEdge = {
|
|
|
337
379
|
*/
|
|
338
380
|
promptKey?: string;
|
|
339
381
|
};
|
|
340
|
-
export type GraphSubagentEdge = Omit<GraphEdge, 'edgeType' | 'condition' | 'promptKey'> & {
|
|
382
|
+
export type GraphSubagentEdge = Omit<GraphEdge, 'edgeType' | 'condition' | 'promptKey' | 'handoffScope'> & {
|
|
341
383
|
edgeType: 'direct';
|
|
342
384
|
condition?: never;
|
|
343
385
|
promptKey?: never;
|
|
386
|
+
handoffScope?: never;
|
|
344
387
|
};
|
|
345
388
|
export type MultiAgentGraphInput = StandardGraphInput & {
|
|
346
389
|
edges: GraphEdge[];
|
|
390
|
+
/** Explicit fresh-turn entry; absent preserves topology-inferred entry points. */
|
|
391
|
+
entryAgentId?: string;
|
|
392
|
+
/** Shared logical-turn handoff cap. Zero forbids handoffs; absent uses only recursion limits. */
|
|
393
|
+
maxHandoffs?: number;
|
|
347
394
|
/** Captures the designated member's final AI turn in graph state. */
|
|
348
395
|
resultAgentId?: string;
|
|
349
396
|
/** Optional per-member Pregel budget when the outer graph has its own topology budget. */
|
|
@@ -101,8 +101,10 @@ export type MultiAgentGraphConfig = {
|
|
|
101
101
|
compileOptions?: g.CompileOptions;
|
|
102
102
|
agents: g.AgentInputs[];
|
|
103
103
|
edges: g.GraphEdge[];
|
|
104
|
+
entryAgentId?: string;
|
|
105
|
+
maxHandoffs?: number;
|
|
104
106
|
};
|
|
105
|
-
export type StandardGraphConfig = Omit<MultiAgentGraphConfig, 'edges' | 'type'> & {
|
|
107
|
+
export type StandardGraphConfig = Omit<MultiAgentGraphConfig, 'edges' | 'type' | 'entryAgentId' | 'maxHandoffs'> & {
|
|
106
108
|
type?: 'standard';
|
|
107
109
|
signal?: AbortSignal;
|
|
108
110
|
};
|
|
@@ -258,6 +260,11 @@ export type RunConfig = {
|
|
|
258
260
|
*/
|
|
259
261
|
langfuse?: g.LangfuseConfig;
|
|
260
262
|
customHandlers?: Record<string, g.EventHandler>;
|
|
263
|
+
/** Explicit client-owned tools for a single-agent graph. A batch mixing
|
|
264
|
+
* client and SDK/provider calls fails closed; omitted means SDK ownership.
|
|
265
|
+
* Hosts must register the corresponding model-facing tool schemas.
|
|
266
|
+
*/
|
|
267
|
+
clientDelegatedToolNames?: readonly string[];
|
|
261
268
|
/**
|
|
262
269
|
* Receives token usage for every model call made inside subagent child
|
|
263
270
|
* runs (including nested subagents). Child graphs execute via `invoke()`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MessageContentImageUrl, MessageContentText, ToolMessage, BaseMessage } from '@langchain/core/messages';
|
|
1
|
+
import type { AIMessageChunk, MessageContentImageUrl, MessageContentText, ToolMessage, BaseMessage } from '@langchain/core/messages';
|
|
2
2
|
import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
|
|
3
3
|
import type { LLMResult, Generation } from '@langchain/core/outputs';
|
|
4
4
|
import type { Command } from '@langchain/langgraph';
|
|
@@ -8,6 +8,21 @@ import type { AssistantTextPhase } from '@/types/assistantPhase';
|
|
|
8
8
|
import type { SummarizeCompleteEvent } from '@/types/summarize';
|
|
9
9
|
import type { ToolEndEvent } from '@/types/tools';
|
|
10
10
|
import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
|
|
11
|
+
/** One accepted model result, detached from execution state before host dispatch.
|
|
12
|
+
* Provider chunks, failed attempts and UI run-step events are not this contract. */
|
|
13
|
+
export interface ModelResponseEvent {
|
|
14
|
+
type: 'model_response';
|
|
15
|
+
/** Graph-generated acceptance ID, not a provider ID or run-step index. */
|
|
16
|
+
id: string;
|
|
17
|
+
agentId: string;
|
|
18
|
+
/** Graph-state message identity used to correlate ToolNode ownership. */
|
|
19
|
+
messageId?: string;
|
|
20
|
+
toolCalls: ReadonlyArray<ToolCall>;
|
|
21
|
+
/** Same index as toolCalls. Only a trusted graph decision of 'client'
|
|
22
|
+
* permits this call on the OpenAI client wire; absence fails closed. */
|
|
23
|
+
toolCallDispositions: ReadonlyArray<'sdk' | 'provider' | 'client'>;
|
|
24
|
+
invalidToolCalls: ReadonlyArray<NonNullable<AIMessageChunk['invalid_tool_calls']>[number]>;
|
|
25
|
+
}
|
|
11
26
|
export type HandleLLMEnd = (output: LLMResult, runId: string, parentRunId?: string, tags?: string[]) => void;
|
|
12
27
|
export type MetadataAggregatorResult = {
|
|
13
28
|
handleLLMEnd: HandleLLMEnd;
|
|
@@ -442,3 +457,9 @@ export type ContentAggregatorResult = {
|
|
|
442
457
|
contentParts: Array<MessageContentComplex | undefined>;
|
|
443
458
|
aggregateContent: ContentAggregator;
|
|
444
459
|
};
|
|
460
|
+
/** Ownership, not successful completion. Interrupted/failed batches remain graph-owned. */
|
|
461
|
+
export interface ModelToolsClaimedEvent {
|
|
462
|
+
type: 'model_tools_claimed';
|
|
463
|
+
agentId: string;
|
|
464
|
+
messageId: string;
|
|
465
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
|
6
6
|
import type { LangfuseConfig, SubagentExecutionContext } from './graph';
|
|
7
7
|
import type { PreparedSubagents } from '@/tools/preparedSubagents';
|
|
8
8
|
import type { RunBreakerScope } from '@/llm/streamLimits';
|
|
9
|
+
import type { HandoffRouting } from '@/graphs/handoff';
|
|
9
10
|
import type { HumanInTheLoopConfig } from './hitl';
|
|
10
11
|
import type { HookRegistry } from '@/hooks';
|
|
11
12
|
/** Replacement type for `import type { ToolCall } from '@langchain/core/messages/tool'` in order to have stringified args typed */
|
|
@@ -103,6 +104,8 @@ export type EagerEventToolCallChunkState = {
|
|
|
103
104
|
sealedArgsFragment?: string;
|
|
104
105
|
};
|
|
105
106
|
export type ToolNodeOptions = {
|
|
107
|
+
/** @internal Admission shared by tool nodes belonging to one multi-agent graph. */
|
|
108
|
+
handoffRouting?: Pick<HandoffRouting, 'finalize'>;
|
|
106
109
|
name?: string;
|
|
107
110
|
tags?: string[];
|
|
108
111
|
/** Enables LangChain/LangGraph tracing for this ToolNode. Defaults to false. */
|
|
@@ -290,6 +293,8 @@ export type ToolNodeOptions = {
|
|
|
290
293
|
restoreRunStepResumeState?: (state?: RunStepResumeState, config?: RunnableConfig) => void;
|
|
291
294
|
/** SDK-owned checkpoint snapshot for open run-step lifecycle state. */
|
|
292
295
|
createRunStepResumeState?: () => RunStepResumeState;
|
|
296
|
+
/** Internal ownership bridge, awaited before ToolNode can execute a batch. */
|
|
297
|
+
onToolCallsClaimed?: (messageId: string, config: RunnableConfig) => Promise<void>;
|
|
293
298
|
};
|
|
294
299
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
295
300
|
export type ToolEndEvent = {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Encode JSON data, not arbitrary JS values. No getters, proxies or toJSON hooks run.
|
|
2
|
+
* Repeated references expand like JSON (and count against the budget); cycles fail.
|
|
3
|
+
* Limits bound output and traversal, including deeply nested/alias-heavy input.
|
|
4
|
+
*/
|
|
5
|
+
export declare function serializeToolArguments(value: unknown, maxBytes: number): string;
|
|
6
|
+
/** Detach JSON data without encoding it. Unlike projection, invocation has no
|
|
7
|
+
* formatting budget: preserve shared references and traverse iteratively so a
|
|
8
|
+
* deep or alias-heavy object cannot expand exponentially during validation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cloneToolArguments(value: unknown): Record<string, unknown>;
|
package/package.json
CHANGED
package/src/common/enum.ts
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
export enum GraphEvents {
|
|
8
8
|
/* Custom Events */
|
|
9
9
|
|
|
10
|
+
/** Accepted graph model result, after fallback selection. Registry-only, not a provider callback. */
|
|
11
|
+
ON_MODEL_RESPONSE = 'on_model_response',
|
|
12
|
+
/** Registry-only: ToolNode has taken ownership of an accepted message's calls. */
|
|
13
|
+
ON_MODEL_TOOLS_CLAIMED = 'on_model_tools_claimed',
|
|
14
|
+
|
|
10
15
|
/** [Custom] Agent update event in multi-agent graph/workflow */
|
|
11
16
|
ON_AGENT_UPDATE = 'on_agent_update',
|
|
12
17
|
/** [Custom] Delta event for run steps (message creation and tool calls) */
|
package/src/events.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { Logger } from 'winston';
|
|
|
8
8
|
import type { MultiAgentGraph, StandardGraph } from '@/graphs';
|
|
9
9
|
import type * as t from '@/types';
|
|
10
10
|
import { dispatchesChatModelStream, SDK_STREAM_DISPATCH } from '@/stream';
|
|
11
|
-
import { Constants } from '@/common';
|
|
11
|
+
import { Constants, GraphEvents } from '@/common';
|
|
12
12
|
|
|
13
13
|
export class HandlerRegistry {
|
|
14
14
|
private handlers: Map<string, t.EventHandler> = new Map();
|
|
@@ -26,12 +26,23 @@ export function composeEventHandlers(
|
|
|
26
26
|
...handlerSets: Array<Record<string, t.EventHandler> | undefined>
|
|
27
27
|
): Record<string, t.EventHandler> {
|
|
28
28
|
const composed: Partial<Record<string, t.EventHandler>> = {};
|
|
29
|
+
const acceptedObservers = new Map<string, t.EventHandler[]>();
|
|
29
30
|
|
|
30
31
|
for (const handlerSet of handlerSets) {
|
|
31
32
|
if (!handlerSet) {
|
|
32
33
|
continue;
|
|
33
34
|
}
|
|
34
35
|
for (const [eventType, handler] of Object.entries(handlerSet)) {
|
|
36
|
+
if (
|
|
37
|
+
eventType === GraphEvents.ON_MODEL_RESPONSE ||
|
|
38
|
+
eventType === GraphEvents.ON_MODEL_TOOLS_CLAIMED
|
|
39
|
+
) {
|
|
40
|
+
const observers = acceptedObservers.get(eventType) ?? [];
|
|
41
|
+
observers.push(handler);
|
|
42
|
+
acceptedObservers.set(eventType, observers);
|
|
43
|
+
composed[eventType] = handler;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
35
46
|
const previous = composed[eventType];
|
|
36
47
|
if (previous === undefined) {
|
|
37
48
|
composed[eventType] = handler;
|
|
@@ -61,6 +72,18 @@ export function composeEventHandlers(
|
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
for (const [eventType, observers] of acceptedObservers) {
|
|
76
|
+
composed[eventType] = {
|
|
77
|
+
handle: async (event, data, metadata, graph): Promise<void> => {
|
|
78
|
+
// Clone from the graph-owned snapshot, not from any preceding observer's
|
|
79
|
+
// possibly mutated argument tree. One bounded copy per observer.
|
|
80
|
+
for (const observer of observers) {
|
|
81
|
+
await observer.handle(event, structuredClone(data), metadata, graph);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
64
87
|
return composed as Record<string, t.EventHandler>;
|
|
65
88
|
}
|
|
66
89
|
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -38,6 +38,7 @@ import type {
|
|
|
38
38
|
} from '@/graphs/graphFactory';
|
|
39
39
|
import type { OverflowRecoveryPlan } from '@/llm/contextOverflowRecovery';
|
|
40
40
|
import type { FallbackErrorContext } from '@/llm/invoke';
|
|
41
|
+
import type { HandoffRouting } from './handoff';
|
|
41
42
|
import type { HookRegistry } from '@/hooks';
|
|
42
43
|
import type * as t from '@/types';
|
|
43
44
|
import {
|
|
@@ -134,6 +135,10 @@ import {
|
|
|
134
135
|
annotateMessagesForLLM,
|
|
135
136
|
ToolOutputReferenceRegistry,
|
|
136
137
|
} from '@/tools/toolOutputReferences';
|
|
138
|
+
import {
|
|
139
|
+
InvalidModelToolCallError,
|
|
140
|
+
snapshotAcceptedModelResponse,
|
|
141
|
+
} from './acceptedModelResponse';
|
|
137
142
|
import {
|
|
138
143
|
prepareProviderRequest,
|
|
139
144
|
usesNativeOpenAIResponses,
|
|
@@ -185,6 +190,7 @@ import { getTruncationStopReason } from '@/llm/truncation';
|
|
|
185
190
|
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
186
191
|
import { AgentContext } from '@/agents/AgentContext';
|
|
187
192
|
import { createFakeStreamingLLM } from '@/llm/fake';
|
|
193
|
+
import { handoffStateAnnotation } from './handoff';
|
|
188
194
|
import { handleToolCalls } from '@/tools/handlers';
|
|
189
195
|
import { isThinkingEnabled } from '@/llm/request';
|
|
190
196
|
import { resolveMaxSeals } from '@/llm/preempt';
|
|
@@ -829,6 +835,8 @@ export abstract class Graph<
|
|
|
829
835
|
callerSignal?: AbortSignal;
|
|
830
836
|
/** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
|
|
831
837
|
invokedToolIds?: Set<string>;
|
|
838
|
+
/** Explicit host policy. Never inferred from missing ToolNode claims. */
|
|
839
|
+
clientDelegatedToolNames?: ReadonlySet<string>;
|
|
832
840
|
handlerRegistry: HandlerRegistry | undefined;
|
|
833
841
|
/** Host registry retained only for forwarding tools from nested child graphs. */
|
|
834
842
|
protected parentToolHandlerRegistry: HandlerRegistry | undefined;
|
|
@@ -1354,6 +1362,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1354
1362
|
subagentUsageSink?: t.SubagentUsageSink;
|
|
1355
1363
|
/** See {@link t.StandardGraphInput.subagentScope}. */
|
|
1356
1364
|
subagentScope: boolean;
|
|
1365
|
+
handoffRouting?: HandoffRouting;
|
|
1357
1366
|
/** See {@link t.StandardGraphInput.subagentTasks}. */
|
|
1358
1367
|
subagentTasks: t.SubagentTaskConfig | undefined;
|
|
1359
1368
|
/** See {@link t.StandardGraphInput.subagentExecutionContext}. */
|
|
@@ -1540,6 +1549,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1540
1549
|
preemption,
|
|
1541
1550
|
streamLimits,
|
|
1542
1551
|
toolExecution,
|
|
1552
|
+
clientDelegatedToolNames,
|
|
1543
1553
|
}: t.StandardGraphInput,
|
|
1544
1554
|
dependencies?: GraphFactoryDependencies
|
|
1545
1555
|
) {
|
|
@@ -1567,6 +1577,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1567
1577
|
this.preemption = preemption;
|
|
1568
1578
|
this.streamLimits = resolveStreamLimits(streamLimits);
|
|
1569
1579
|
this.toolExecution = toolExecution;
|
|
1580
|
+
if (clientDelegatedToolNames != null && clientDelegatedToolNames.length > 0) {
|
|
1581
|
+
if (agents.length !== 1) {
|
|
1582
|
+
throw new Error('Client tool delegation requires a single-agent graph');
|
|
1583
|
+
}
|
|
1584
|
+
if (clientDelegatedToolNames.some((name) => !name.trim())) {
|
|
1585
|
+
throw new Error('Client delegated tool names must be nonempty');
|
|
1586
|
+
}
|
|
1587
|
+
this.clientDelegatedToolNames = new Set(clientDelegatedToolNames);
|
|
1588
|
+
}
|
|
1570
1589
|
|
|
1571
1590
|
if (agents.length === 0) {
|
|
1572
1591
|
throw new Error('At least one agent configuration is required');
|
|
@@ -2830,6 +2849,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2830
2849
|
currentToolMap?: t.ToolMap;
|
|
2831
2850
|
agentContext?: AgentContext;
|
|
2832
2851
|
}): CustomToolNode<t.BaseGraphState> | ToolNode<t.BaseGraphState> {
|
|
2852
|
+
const onToolCallsClaimed = async (
|
|
2853
|
+
messageId: string,
|
|
2854
|
+
config: RunnableConfig
|
|
2855
|
+
): Promise<void> => {
|
|
2856
|
+
const handler = this.handlerRegistry?.getHandler(
|
|
2857
|
+
GraphEvents.ON_MODEL_TOOLS_CLAIMED
|
|
2858
|
+
);
|
|
2859
|
+
if (handler == null) return;
|
|
2860
|
+
await handler.handle(
|
|
2861
|
+
GraphEvents.ON_MODEL_TOOLS_CLAIMED,
|
|
2862
|
+
{
|
|
2863
|
+
type: 'model_tools_claimed',
|
|
2864
|
+
agentId: agentContext?.agentId ?? this.defaultAgentId,
|
|
2865
|
+
messageId,
|
|
2866
|
+
},
|
|
2867
|
+
config.metadata,
|
|
2868
|
+
this
|
|
2869
|
+
);
|
|
2870
|
+
};
|
|
2833
2871
|
const toolDefinitions = agentContext?.toolDefinitions;
|
|
2834
2872
|
const eventDrivenMode =
|
|
2835
2873
|
toolDefinitions != null && toolDefinitions.length > 0;
|
|
@@ -2871,6 +2909,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2871
2909
|
}
|
|
2872
2910
|
|
|
2873
2911
|
const node = new CustomToolNode<t.BaseGraphState>({
|
|
2912
|
+
handoffRouting: this.handoffRouting,
|
|
2874
2913
|
tools: allTools,
|
|
2875
2914
|
toolMap: allToolMap,
|
|
2876
2915
|
trace: traceToolNode,
|
|
@@ -2916,6 +2955,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2916
2955
|
this.config = config;
|
|
2917
2956
|
this.restoreRunStepResumeState(state);
|
|
2918
2957
|
},
|
|
2958
|
+
onToolCallsClaimed,
|
|
2919
2959
|
createRunStepResumeState: (): t.RunStepResumeState =>
|
|
2920
2960
|
this.createRunStepResumeState(),
|
|
2921
2961
|
errorHandler: (data, metadata): Promise<boolean> =>
|
|
@@ -2960,6 +3000,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2960
3000
|
: currentToolMap;
|
|
2961
3001
|
|
|
2962
3002
|
const node = new CustomToolNode<t.BaseGraphState>({
|
|
3003
|
+
handoffRouting: this.handoffRouting,
|
|
2963
3004
|
tools: allTraditionalTools,
|
|
2964
3005
|
toolMap: traditionalToolMap,
|
|
2965
3006
|
trace: traceToolNode,
|
|
@@ -2997,6 +3038,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2997
3038
|
this.config = config;
|
|
2998
3039
|
this.restoreRunStepResumeState(state);
|
|
2999
3040
|
},
|
|
3041
|
+
onToolCallsClaimed,
|
|
3000
3042
|
createRunStepResumeState: (): t.RunStepResumeState =>
|
|
3001
3043
|
this.createRunStepResumeState(),
|
|
3002
3044
|
});
|
|
@@ -4358,6 +4400,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4358
4400
|
* succeeding fallback would resolve a run the public contract says
|
|
4359
4401
|
* must reject. Rethrow before any recovery path.
|
|
4360
4402
|
*/
|
|
4403
|
+
if (primaryError instanceof InvalidModelToolCallError) {
|
|
4404
|
+
throw primaryError;
|
|
4405
|
+
}
|
|
4361
4406
|
if (
|
|
4362
4407
|
primaryError instanceof StreamLimitExceededError ||
|
|
4363
4408
|
primaryError instanceof PreparedSubagentError
|
|
@@ -4698,6 +4743,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4698
4743
|
})
|
|
4699
4744
|
);
|
|
4700
4745
|
} catch (fallbackError) {
|
|
4746
|
+
if (fallbackError instanceof InvalidModelToolCallError) {
|
|
4747
|
+
throw fallbackError;
|
|
4748
|
+
}
|
|
4701
4749
|
if (
|
|
4702
4750
|
fallbackError instanceof StreamLimitExceededError ||
|
|
4703
4751
|
fallbackError instanceof PreparedSubagentError
|
|
@@ -5002,6 +5050,36 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5002
5050
|
this.preemptIncomplete = true;
|
|
5003
5051
|
}
|
|
5004
5052
|
|
|
5053
|
+
const responseHandler = this.handlerRegistry?.getHandler(
|
|
5054
|
+
GraphEvents.ON_MODEL_RESPONSE
|
|
5055
|
+
);
|
|
5056
|
+
if (responseHandler != null && responseMessage?.getType() === 'ai') {
|
|
5057
|
+
try {
|
|
5058
|
+
// One graph-owned accepted result after all primary/fallback/overflow paths.
|
|
5059
|
+
// No inference from provider chunks, run-step IDs, or attempt callback metadata.
|
|
5060
|
+
invokeConfig.signal?.throwIfAborted();
|
|
5061
|
+
const accepted = snapshotAcceptedModelResponse(
|
|
5062
|
+
responseMessage as AIMessageChunk,
|
|
5063
|
+
v4(),
|
|
5064
|
+
agentId,
|
|
5065
|
+
this.invokedToolIds,
|
|
5066
|
+
this.clientDelegatedToolNames
|
|
5067
|
+
);
|
|
5068
|
+
// Awaited, registry-only: no trace replay, usage recording or side effects.
|
|
5069
|
+
// Detached calls prevent a consumer from changing tools about to execute.
|
|
5070
|
+
await responseHandler.handle(
|
|
5071
|
+
GraphEvents.ON_MODEL_RESPONSE,
|
|
5072
|
+
accepted,
|
|
5073
|
+
metadata,
|
|
5074
|
+
this
|
|
5075
|
+
);
|
|
5076
|
+
invokeConfig.signal?.throwIfAborted();
|
|
5077
|
+
} catch (error) {
|
|
5078
|
+
this.cleanupSignalListener();
|
|
5079
|
+
throw error;
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
|
|
5005
5083
|
this.cleanupSignalListener();
|
|
5006
5084
|
return result;
|
|
5007
5085
|
};
|
|
@@ -5434,6 +5512,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5434
5512
|
): Promise<Partial<t.AgentSubgraphState>> => {
|
|
5435
5513
|
this.config = config;
|
|
5436
5514
|
this.restoreRunStepResumeState(state.runStepState);
|
|
5515
|
+
this.handoffRouting?.restore(state.handoffState);
|
|
5437
5516
|
const result = await invoke();
|
|
5438
5517
|
/** An ordinary run on a checkpointed thread inherits the last
|
|
5439
5518
|
* compaction's summary in state; it is not this run's output. */
|
|
@@ -5469,6 +5548,28 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5469
5548
|
if (this.summarizeOnlyAgentId != null) {
|
|
5470
5549
|
return END;
|
|
5471
5550
|
}
|
|
5551
|
+
const delegatedNames = this.clientDelegatedToolNames;
|
|
5552
|
+
if (delegatedNames != null && delegatedNames.size > 0) {
|
|
5553
|
+
const { messages } = state as t.BaseGraphState;
|
|
5554
|
+
const last = messages[messages.length - 1] as AIMessageChunk | undefined;
|
|
5555
|
+
const calls = last?.getType() === 'ai' ? last.tool_calls ?? [] : [];
|
|
5556
|
+
if (calls.some((call) => delegatedNames.has(call.name))) {
|
|
5557
|
+
if (
|
|
5558
|
+
calls.some(
|
|
5559
|
+
(call) =>
|
|
5560
|
+
!delegatedNames.has(call.name) ||
|
|
5561
|
+
(call.id != null && this.invokedToolIds?.has(call.id) === true)
|
|
5562
|
+
) ||
|
|
5563
|
+
(last?.invalid_tool_calls?.length ?? 0) > 0 ||
|
|
5564
|
+
getTruncationStopReason(last) != null
|
|
5565
|
+
) {
|
|
5566
|
+
throw new InvalidModelToolCallError(
|
|
5567
|
+
'Mixed client and graph-owned tool calls require separate model turns'
|
|
5568
|
+
);
|
|
5569
|
+
}
|
|
5570
|
+
return END;
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5472
5573
|
const decision = toolsCondition(
|
|
5473
5574
|
state as t.BaseGraphState,
|
|
5474
5575
|
toolNode,
|
|
@@ -5512,6 +5613,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5512
5613
|
default: () => undefined,
|
|
5513
5614
|
}),
|
|
5514
5615
|
runStepState: this.createRunStepStateAnnotation(),
|
|
5616
|
+
handoffState: handoffStateAnnotation(),
|
|
5515
5617
|
});
|
|
5516
5618
|
|
|
5517
5619
|
const readChargeCredits = ():
|
|
@@ -5727,6 +5829,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5727
5829
|
default: () => undefined,
|
|
5728
5830
|
}),
|
|
5729
5831
|
runStepState: this.createRunStepStateAnnotation(),
|
|
5832
|
+
handoffState: handoffStateAnnotation(),
|
|
5730
5833
|
});
|
|
5731
5834
|
const compactingAgentNode = async (
|
|
5732
5835
|
state: t.AgentSubgraphState,
|