@librechat/agents 3.9.3 → 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 +41 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/acceptedModelResponse.cjs +168 -0
- package/dist/cjs/graphs/acceptedModelResponse.cjs.map +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 +2 -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 +8 -1
- 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 +2 -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 +41 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/acceptedModelResponse.mjs +165 -0
- package/dist/esm/graphs/acceptedModelResponse.mjs.map +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 +3 -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 +8 -1
- 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 +2 -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 +3 -1
- package/dist/types/graphs/acceptedModelResponse.d.ts +15 -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 +1 -0
- package/dist/types/tools/ToolNode.d.ts +1 -1
- package/dist/types/types/graph.d.ts +5 -3
- package/dist/types/types/run.d.ts +5 -0
- package/dist/types/types/stream.d.ts +22 -1
- package/dist/types/types/tools.d.ts +2 -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 +95 -0
- package/src/graphs/acceptedModelResponse.ts +307 -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 +21 -0
- package/src/session/AgentSession.ts +2 -2
- package/src/stream.ts +21 -1
- package/src/tools/ToolNode.ts +5 -0
- package/src/tools/subagent/SubagentReplay.ts +10 -9
- package/src/types/graph.ts +15 -9
- package/src/types/run.ts +5 -0
- package/src/types/stream.ts +26 -0
- package/src/types/tools.ts +2 -0
- package/src/utils/acceptedToolArguments.ts +204 -0
|
@@ -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?;
|
|
@@ -229,7 +229,7 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
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, }: t.ToolNodeConstructorParams);
|
|
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);
|
|
233
233
|
invoke(input: any, options?: Partial<RunnableConfig>): Promise<any>;
|
|
234
234
|
private withToolScope;
|
|
235
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';
|
|
@@ -127,7 +127,7 @@ export interface ContextUsageEvent {
|
|
|
127
127
|
* `RunConfig.fadingTiers[agentId]`.
|
|
128
128
|
*/
|
|
129
129
|
export interface FadingTier {
|
|
130
|
-
v:
|
|
130
|
+
v: 2;
|
|
131
131
|
/** Token budget the caps derive from, in raw token space. Never grows;
|
|
132
132
|
* clamped to the current context window when seeded. */
|
|
133
133
|
budgetTokens: number;
|
|
@@ -139,7 +139,7 @@ export interface FadingTier {
|
|
|
139
139
|
/** Latched fading tiers keyed by agent ID. */
|
|
140
140
|
export type FadingTiers = Record<string, FadingTier>;
|
|
141
141
|
export interface EventHandler {
|
|
142
|
-
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 | {
|
|
143
143
|
result: ToolEndEvent;
|
|
144
144
|
}, metadata?: Record<string, unknown>, graph?: StandardGraph | MultiAgentGraph): void | Promise<void>;
|
|
145
145
|
}
|
|
@@ -291,6 +291,8 @@ export type StandardGraphInput = {
|
|
|
291
291
|
agents: AgentInputs[];
|
|
292
292
|
/** Execution backend used to resolve the effective tool registry. */
|
|
293
293
|
toolExecution?: ToolExecutionConfig;
|
|
294
|
+
/** Trusted single-agent client delegation policy; mixed batches fail closed. */
|
|
295
|
+
clientDelegatedToolNames?: readonly string[];
|
|
294
296
|
langfuse?: LangfuseConfig;
|
|
295
297
|
tokenCounter?: TokenCounter;
|
|
296
298
|
indexTokenCountMap?: Record<string, number>;
|
|
@@ -260,6 +260,11 @@ export type RunConfig = {
|
|
|
260
260
|
*/
|
|
261
261
|
langfuse?: g.LangfuseConfig;
|
|
262
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[];
|
|
263
268
|
/**
|
|
264
269
|
* Receives token usage for every model call made inside subagent child
|
|
265
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
|
+
}
|
|
@@ -293,6 +293,8 @@ export type ToolNodeOptions = {
|
|
|
293
293
|
restoreRunStepResumeState?: (state?: RunStepResumeState, config?: RunnableConfig) => void;
|
|
294
294
|
/** SDK-owned checkpoint snapshot for open run-step lifecycle state. */
|
|
295
295
|
createRunStepResumeState?: () => RunStepResumeState;
|
|
296
|
+
/** Internal ownership bridge, awaited before ToolNode can execute a batch. */
|
|
297
|
+
onToolCallsClaimed?: (messageId: string, config: RunnableConfig) => Promise<void>;
|
|
296
298
|
};
|
|
297
299
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
298
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
|
@@ -135,6 +135,10 @@ import {
|
|
|
135
135
|
annotateMessagesForLLM,
|
|
136
136
|
ToolOutputReferenceRegistry,
|
|
137
137
|
} from '@/tools/toolOutputReferences';
|
|
138
|
+
import {
|
|
139
|
+
InvalidModelToolCallError,
|
|
140
|
+
snapshotAcceptedModelResponse,
|
|
141
|
+
} from './acceptedModelResponse';
|
|
138
142
|
import {
|
|
139
143
|
prepareProviderRequest,
|
|
140
144
|
usesNativeOpenAIResponses,
|
|
@@ -831,6 +835,8 @@ export abstract class Graph<
|
|
|
831
835
|
callerSignal?: AbortSignal;
|
|
832
836
|
/** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
|
|
833
837
|
invokedToolIds?: Set<string>;
|
|
838
|
+
/** Explicit host policy. Never inferred from missing ToolNode claims. */
|
|
839
|
+
clientDelegatedToolNames?: ReadonlySet<string>;
|
|
834
840
|
handlerRegistry: HandlerRegistry | undefined;
|
|
835
841
|
/** Host registry retained only for forwarding tools from nested child graphs. */
|
|
836
842
|
protected parentToolHandlerRegistry: HandlerRegistry | undefined;
|
|
@@ -1543,6 +1549,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1543
1549
|
preemption,
|
|
1544
1550
|
streamLimits,
|
|
1545
1551
|
toolExecution,
|
|
1552
|
+
clientDelegatedToolNames,
|
|
1546
1553
|
}: t.StandardGraphInput,
|
|
1547
1554
|
dependencies?: GraphFactoryDependencies
|
|
1548
1555
|
) {
|
|
@@ -1570,6 +1577,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1570
1577
|
this.preemption = preemption;
|
|
1571
1578
|
this.streamLimits = resolveStreamLimits(streamLimits);
|
|
1572
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
|
+
}
|
|
1573
1589
|
|
|
1574
1590
|
if (agents.length === 0) {
|
|
1575
1591
|
throw new Error('At least one agent configuration is required');
|
|
@@ -2833,6 +2849,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2833
2849
|
currentToolMap?: t.ToolMap;
|
|
2834
2850
|
agentContext?: AgentContext;
|
|
2835
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
|
+
};
|
|
2836
2871
|
const toolDefinitions = agentContext?.toolDefinitions;
|
|
2837
2872
|
const eventDrivenMode =
|
|
2838
2873
|
toolDefinitions != null && toolDefinitions.length > 0;
|
|
@@ -2920,6 +2955,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2920
2955
|
this.config = config;
|
|
2921
2956
|
this.restoreRunStepResumeState(state);
|
|
2922
2957
|
},
|
|
2958
|
+
onToolCallsClaimed,
|
|
2923
2959
|
createRunStepResumeState: (): t.RunStepResumeState =>
|
|
2924
2960
|
this.createRunStepResumeState(),
|
|
2925
2961
|
errorHandler: (data, metadata): Promise<boolean> =>
|
|
@@ -3002,6 +3038,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3002
3038
|
this.config = config;
|
|
3003
3039
|
this.restoreRunStepResumeState(state);
|
|
3004
3040
|
},
|
|
3041
|
+
onToolCallsClaimed,
|
|
3005
3042
|
createRunStepResumeState: (): t.RunStepResumeState =>
|
|
3006
3043
|
this.createRunStepResumeState(),
|
|
3007
3044
|
});
|
|
@@ -4363,6 +4400,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4363
4400
|
* succeeding fallback would resolve a run the public contract says
|
|
4364
4401
|
* must reject. Rethrow before any recovery path.
|
|
4365
4402
|
*/
|
|
4403
|
+
if (primaryError instanceof InvalidModelToolCallError) {
|
|
4404
|
+
throw primaryError;
|
|
4405
|
+
}
|
|
4366
4406
|
if (
|
|
4367
4407
|
primaryError instanceof StreamLimitExceededError ||
|
|
4368
4408
|
primaryError instanceof PreparedSubagentError
|
|
@@ -4703,6 +4743,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4703
4743
|
})
|
|
4704
4744
|
);
|
|
4705
4745
|
} catch (fallbackError) {
|
|
4746
|
+
if (fallbackError instanceof InvalidModelToolCallError) {
|
|
4747
|
+
throw fallbackError;
|
|
4748
|
+
}
|
|
4706
4749
|
if (
|
|
4707
4750
|
fallbackError instanceof StreamLimitExceededError ||
|
|
4708
4751
|
fallbackError instanceof PreparedSubagentError
|
|
@@ -5007,6 +5050,36 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5007
5050
|
this.preemptIncomplete = true;
|
|
5008
5051
|
}
|
|
5009
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
|
+
|
|
5010
5083
|
this.cleanupSignalListener();
|
|
5011
5084
|
return result;
|
|
5012
5085
|
};
|
|
@@ -5475,6 +5548,28 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5475
5548
|
if (this.summarizeOnlyAgentId != null) {
|
|
5476
5549
|
return END;
|
|
5477
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
|
+
}
|
|
5478
5573
|
const decision = toolsCondition(
|
|
5479
5574
|
state as t.BaseGraphState,
|
|
5480
5575
|
toolNode,
|