@librechat/agents 3.2.54 → 3.2.56

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.
@@ -4,6 +4,7 @@ const require_core = require("../messages/core.cjs");
4
4
  require("../messages/index.cjs");
5
5
  const require_toolOutputReferences = require("../tools/toolOutputReferences.cjs");
6
6
  const require_stream = require("../stream.cjs");
7
+ const require_truncation = require("./truncation.cjs");
7
8
  const require_providers = require("./providers.cjs");
8
9
  const require_init = require("./init.cjs");
9
10
  let _langchain_core_messages = require("@langchain/core/messages");
@@ -137,10 +138,12 @@ async function attemptInvoke({ model, messages, provider, context, onChunk }, co
137
138
  }
138
139
  if (require_providers.manualToolStreamProviders.has(provider)) finalChunk = require_core.modifyDeltaProperties(provider, finalChunk);
139
140
  if ((finalChunk?.tool_calls?.length ?? 0) > 0) finalChunk.tool_calls = finalChunk.tool_calls?.filter((tool_call) => !!tool_call.name);
141
+ require_truncation.assertNotTruncatedToolCall(finalChunk, provider);
140
142
  return { messages: [finalChunk] };
141
143
  }
142
144
  const finalMessage = await model.invoke(messagesForProvider, config);
143
145
  if ((finalMessage.tool_calls?.length ?? 0) > 0) finalMessage.tool_calls = finalMessage.tool_calls?.filter((tool_call) => !!tool_call.name);
146
+ require_truncation.assertNotTruncatedToolCall(finalMessage, provider);
144
147
  return { messages: [finalMessage] };
145
148
  }
146
149
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"invoke.cjs","names":["ChatModelStreamHandler","AIMessageChunk","annotateMessagesForLLM","manualToolStreamProviders","modifyDeltaProperties","initializeModel"],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { Constants, GraphEvents, Providers } from '@/common';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n /**\n * Stamp the provider that is ACTUALLY serving this invocation onto the\n * callback metadata. `attemptInvoke` is the single funnel for primary,\n * fallback, and summarization model calls, so consumers that need\n * provider attribution per call (the subagent usage-capture handler)\n * read this key instead of trusting static agent config — which is\n * wrong for fallback-served calls — or `ls_provider` — which derived\n * providers inherit from their base class.\n */\n config = {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_PROVIDER]: provider,\n },\n };\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n return { messages: [finalMessage] };\n}\n\n/**\n * Best-effort read of the configured model name from client options.\n * Providers disagree on the key (`model` vs `modelName`).\n */\nfunction extractClientOptionsModel(\n clientOptions: t.ClientOptions | undefined\n): string | undefined {\n const options = clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof options?.model === 'string' && options.model !== '') {\n return options.model;\n }\n if (typeof options?.modelName === 'string' && options.modelName !== '') {\n return options.modelName;\n }\n return undefined;\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n * Throws the last error if all fallbacks fail.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n}: {\n fallbacks: Array<{ provider: Providers; clientOptions?: t.ClientOptions }>;\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n let lastError: unknown = primaryError;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n /**\n * Stamp the fallback's configured model onto callback metadata so\n * per-call attribution (subagent usage capture) doesn't fall back to\n * the PRIMARY config's model when the provider reports no\n * `ls_model_name`. The serving provider is stamped uniformly by\n * `attemptInvoke` (`INVOKED_PROVIDER`).\n */\n const fbModelName = extractClientOptionsModel(fb.clientOptions);\n const fbConfig: RunnableConfig | undefined =\n fbModelName == null\n ? config\n : {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_MODEL]: fbModelName,\n },\n };\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n fbConfig\n );\n return result;\n } catch (e) {\n lastError = e;\n continue;\n }\n }\n if (lastError !== undefined) {\n throw lastError;\n }\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"invoke.cjs","names":["ChatModelStreamHandler","AIMessageChunk","annotateMessagesForLLM","manualToolStreamProviders","modifyDeltaProperties","initializeModel"],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { assertNotTruncatedToolCall } from '@/llm/truncation';\nimport { Constants, GraphEvents, Providers } from '@/common';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n /**\n * Stamp the provider that is ACTUALLY serving this invocation onto the\n * callback metadata. `attemptInvoke` is the single funnel for primary,\n * fallback, and summarization model calls, so consumers that need\n * provider attribution per call (the subagent usage-capture handler)\n * read this key instead of trusting static agent config — which is\n * wrong for fallback-served calls — or `ls_provider` — which derived\n * providers inherit from their base class.\n */\n config = {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_PROVIDER]: provider,\n },\n };\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n assertNotTruncatedToolCall(finalChunk, provider);\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n assertNotTruncatedToolCall(finalMessage, provider);\n return { messages: [finalMessage] };\n}\n\n/**\n * Best-effort read of the configured model name from client options.\n * Providers disagree on the key (`model` vs `modelName`).\n */\nfunction extractClientOptionsModel(\n clientOptions: t.ClientOptions | undefined\n): string | undefined {\n const options = clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof options?.model === 'string' && options.model !== '') {\n return options.model;\n }\n if (typeof options?.modelName === 'string' && options.modelName !== '') {\n return options.modelName;\n }\n return undefined;\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n * Throws the last error if all fallbacks fail.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n}: {\n fallbacks: Array<{ provider: Providers; clientOptions?: t.ClientOptions }>;\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n let lastError: unknown = primaryError;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n /**\n * Stamp the fallback's configured model onto callback metadata so\n * per-call attribution (subagent usage capture) doesn't fall back to\n * the PRIMARY config's model when the provider reports no\n * `ls_model_name`. The serving provider is stamped uniformly by\n * `attemptInvoke` (`INVOKED_PROVIDER`).\n */\n const fbModelName = extractClientOptionsModel(fb.clientOptions);\n const fbConfig: RunnableConfig | undefined =\n fbModelName == null\n ? config\n : {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_MODEL]: fbModelName,\n },\n };\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n fbConfig\n );\n return result;\n } catch (e) {\n lastError = e;\n continue;\n }\n }\n if (lastError !== undefined) {\n throw lastError;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;AAsDA,SAAS,sCACP,SACoC;CACpC,MAAM,UAAU,SAAS,iBAAiB,WAAA,sBAE1C;CACA,OAAO,mBAAmBA,eAAAA,yBAAyB,UAAU,KAAA;AAC/D;AAEA,SAAS,oBAAoB,OAAgC;CAC3D,MAAM,mBAAmB,MAAM,kBAAkB;CACjD,OAAO,MAAM,QAAQ,gBAAgB,KAAK,iBAAiB,SAAS;AACtE;AAEA,SAAS,4CAA4C,EACnD,SACA,MACA,YAKiB;CACjB,MAAM,UAAU,mCAAmC;EACjD;EACA;EACA;CACF,CAAC;CACD,IAAI,WAAW,QAAQ,YAAY,KAAK,SACtC,OAAO;CAGT,OAAO,IAAIC,yBAAAA,eACT,OAAO,OAAO,CAAC,GAAG,MAAM,EACtB,QACF,CAAC,CACH;AACF;AAEA,SAAS,mCAAmC,EAC1C,SACA,MACA,YAKqB;CACrB,IACE,aAAA,gBACA,WAAW,QACX,CAAC,oBAAoB,IAAI,KACzB,OAAO,QAAQ,YAAY,YAC3B,QAAQ,YAAY,MACpB,OAAO,KAAK,YAAY,YACxB,KAAK,YAAY,IAEjB;CAEF,IAAI,CAAC,KAAK,QAAQ,WAAW,QAAQ,OAAO,GAC1C,OAAO,KAAK;CAEd,OAAO,KAAK,QAAQ,MAAM,QAAQ,QAAQ,MAAM;AAClD;AAEA,SAAS,uBACP,kBACqC;CACrC,OAAO,OAAO,YACZ,OAAO,QAAQ,gBAAgB,CAAC,CAAC,QAC9B,CAAC,SAAS,QAAQ,mBACrB,CACF;AACF;AAEA,SAAS,uBAAuB,EAC9B,SACA,MACA,YAK6B;CAC7B,MAAM,UAAU,mCAAmC;EACjD;EACA;EACA;CACF,CAAC;CACD,IAAI,WAAW,MACb,OAAO;CAET,IAAI,YAAY,IACd;CAEF,OAAO,IAAIA,yBAAAA,eACT,OAAO,OAAO,CAAC,GAAG,MAAM;EACtB;EACA,mBAAmB,uBAAuB,KAAK,iBAAiB;CAClE,CAAC,CACH;AACF;AAEA,SAAS,kBAAkB,EACzB,SACA,MACA,YAKiB;CACjB,IAAI,WAAW,MACb,OAAO;CAET,QAAA,GAAA,6BAAA,OAAA,CACE,SACA,4CAA4C;EAAE;EAAS;EAAM;CAAS,CAAC,CACzE;AACF;;;;;;;;;;AAWA,eAAsB,cACpB,EACE,OACA,UACA,UACA,SACA,WAQF,QACoC;;;;;;;;CAQpC,MAAM,WAAW,SAAS,8BAA8B;CACxD,MAAM,QAAQ,QAAQ,cAAc;CACpC,MAAM,sBAAsBC,6BAAAA,uBAAuB,UAAU,UAAU,KAAK;;;;;;;;;;CAW5E,SAAS;EACP,GAAG;EACH,UAAU;GACR,GAAI,QAAQ,YAAY,CAAC;2BACK;EAChC;CACF;CAEA,IAAI,MAAM,QAAQ;EAChB,MAAM,SAAS,MAAM,MAAM,OAAO,qBAAqB,MAAM;EAC7D,IAAI;EACJ,MAAM,0BACJ,sCAAsC,OAAO;EAE/C,IAAI,SACF,WAAW,MAAM,SAAS,QAAQ;GAChC,MAAM,QAAQ,KAAK;GACnB,aAAa,kBAAkB;IAC7B,SAAS;IACT,MAAM;IACN;GACF,CAAC;EACH;OACK,IAAI,2BAA2B,MAAM;GAC1C,MAAM,WAAW,OAAO;GACxB,MAAM,gBAAgB,IAAIF,eAAAA,uBAAuB;GACjD,WAAW,MAAM,SAAS,QAAQ;IAChC,MAAM,gBAAgB,uBAAuB;KAC3C,SAAS;KACT,MAAM;KACN;IACF,CAAC;IACD,IAAI,iBAAiB,MACnB,MAAM,cAAc,OAAA,wBAElB,EAAE,OAAO,cAAc,GACvB,UACA,OACF;IAEF,aAAa,kBAAkB;KAC7B,SAAS;KACT,MAAM;KACN;IACF,CAAC;GACH;EACF,OAAO;GACL,MAAM,WAAW,OAAO;GACxB,WAAW,MAAM,SAAS,QAAQ;IAChC,MAAM,gBAAgB,uBAAuB;KAC3C,SAAS;KACT,MAAM;KACN;IACF,CAAC;IACD,IAAI,iBAAiB,QAAQ,kBAAkB,OAC7C,MAAM,wBAAwB,OAAA,wBAE5B,EAAE,OAAO,cAAc,GACvB,UACA,OACF;IAEF,aAAa,kBAAkB;KAC7B,SAAS;KACT,MAAM;KACN;IACF,CAAC;GACH;EACF;EAEA,IAAIG,kBAAAA,0BAA0B,IAAI,QAAQ,GACxC,aAAaC,aAAAA,sBAAsB,UAAU,UAAU;EAGzD,KAAK,YAAY,YAAY,UAAU,KAAK,GAC1C,WAAY,aAAa,WAAY,YAAY,QAC9C,cAAwB,CAAC,CAAC,UAAU,IACvC;EAGF,mBAAA,2BAA2B,YAAY,QAAQ;EAC/C,OAAO,EAAE,UAAU,CAAC,UAA4B,EAAE;CACpD;CAEA,MAAM,eAAe,MAAM,MAAM,OAAO,qBAAqB,MAAM;CACnE,KAAK,aAAa,YAAY,UAAU,KAAK,GAC3C,aAAa,aAAa,aAAa,YAAY,QAChD,cAAwB,CAAC,CAAC,UAAU,IACvC;CAEF,mBAAA,2BAA2B,cAAc,QAAQ;CACjD,OAAO,EAAE,UAAU,CAAC,YAAY,EAAE;AACpC;;;;;AAMA,SAAS,0BACP,eACoB;CACpB,MAAM,UAAU;CAGhB,IAAI,OAAO,SAAS,UAAU,YAAY,QAAQ,UAAU,IAC1D,OAAO,QAAQ;CAEjB,IAAI,OAAO,SAAS,cAAc,YAAY,QAAQ,cAAc,IAClE,OAAO,QAAQ;AAGnB;;;;;AAMA,eAAsB,qBAAqB,EACzC,WACA,OACA,UACA,QACA,cACA,SACA,WASiD;CACjD,IAAI,YAAqB;CACzB,KAAK,MAAM,MAAM,WACf,IAAI;EACF,MAAM,UAAUC,aAAAA,gBAAgB;GAC9B,UAAU,GAAG;GACb,eAAe,GAAG;GAClB;EACF,CAAC;;;;;;;;EAQD,MAAM,cAAc,0BAA0B,GAAG,aAAa;EAC9D,MAAM,WACJ,eAAe,OACX,SACA;GACA,GAAG;GACH,UAAU;IACR,GAAI,QAAQ,YAAY,CAAC;yBACE;GAC7B;EACF;EAWJ,OAAO,MAVc,cACnB;GACE,OAAO;GACP;GACA,UAAU,GAAG;GACb;GACA;EACF,GACA,QACF;CAEF,SAAS,GAAG;EACV,YAAY;EACZ;CACF;CAEF,IAAI,cAAc,KAAA,GAChB,MAAM;AAGV"}
@@ -0,0 +1,110 @@
1
+ require("../common/enum.cjs");
2
+ require("../common/index.cjs");
3
+ //#region src/llm/truncation.ts
4
+ /**
5
+ * Providers whose adapters deliver tool calls as complete, atomic objects
6
+ * rather than streamed argument deltas. Google/Vertex (GenAI) emit each
7
+ * `functionCall` whole and seal it on arrival, so a `MAX_TOKENS` finish does
8
+ * NOT imply the arguments were cut off — the truncation guard must skip them.
9
+ */
10
+ const ATOMIC_TOOL_CALL_ARG_PROVIDERS = new Set(["google", "vertexai"]);
11
+ const MAX_TOKEN_VALUES = new Set([
12
+ "max_tokens",
13
+ "max_token",
14
+ "maxtokens",
15
+ "max_output_tokens"
16
+ ]);
17
+ const LENGTH_VALUES = new Set(["length"]);
18
+ function normalizeStopValue(value) {
19
+ if (typeof value !== "string") return null;
20
+ const normalized = value.trim().toLowerCase();
21
+ if (MAX_TOKEN_VALUES.has(normalized)) return "max_tokens";
22
+ if (LENGTH_VALUES.has(normalized)) return "length";
23
+ return null;
24
+ }
25
+ /**
26
+ * Reads the truncation stop reason off a message, covering every provider
27
+ * shape we stream:
28
+ * - `response_metadata.stopReason` (Bedrock Converse, non-streaming)
29
+ * - `response_metadata.messageStop.stopReason` (Bedrock Converse streaming)
30
+ * - `response_metadata.stop_reason` (Anthropic, non-streaming)
31
+ * - `additional_kwargs.stop_reason` (Anthropic streaming `message_delta`)
32
+ * - `response_metadata.finish_reason` (OpenAI chat-completions / compatible)
33
+ * - `response_metadata.incomplete_details.reason` (OpenAI Responses API)
34
+ * - `response_metadata.finishReason` (Google)
35
+ *
36
+ * Returns the normalized reason when the model stopped because it hit the
37
+ * output token ceiling, otherwise `null`.
38
+ */
39
+ function getTruncationStopReason(message) {
40
+ const meta = message?.response_metadata;
41
+ const additionalKwargs = message?.additional_kwargs;
42
+ if (meta == null && additionalKwargs == null) return null;
43
+ const messageStop = meta?.messageStop;
44
+ const incompleteDetails = meta?.incomplete_details;
45
+ const candidates = [
46
+ meta?.stopReason,
47
+ meta?.stop_reason,
48
+ meta?.finish_reason,
49
+ meta?.finishReason,
50
+ messageStop?.stopReason,
51
+ incompleteDetails?.reason,
52
+ additionalKwargs?.stop_reason
53
+ ];
54
+ for (const candidate of candidates) {
55
+ const normalized = normalizeStopValue(candidate);
56
+ if (normalized != null) return normalized;
57
+ }
58
+ return null;
59
+ }
60
+ function hasOpenToolCall(message) {
61
+ const m = message;
62
+ return (m.tool_calls?.length ?? 0) > 0 || (m.tool_call_chunks?.length ?? 0) > 0 || (m.invalid_tool_calls?.length ?? 0) > 0;
63
+ }
64
+ /**
65
+ * Error raised when a model turn is cut off by the output token limit while it
66
+ * was still emitting a tool call. The arguments are necessarily incomplete, so
67
+ * executing or re-prompting the call would loop on a malformed request. Failing
68
+ * fast with this surfaces an actionable message to the caller instead.
69
+ */
70
+ var OutputTruncationError = class extends Error {
71
+ stopReason;
72
+ toolCallNames;
73
+ constructor(stopReason, toolCallNames) {
74
+ const named = toolCallNames.length > 0 ? ` (tool call: ${toolCallNames.join(", ")})` : "";
75
+ super(`The model response was truncated at the maximum output token limit before the tool call arguments were complete${named}. Increase the model's max output tokens, or have the model produce smaller arguments — for large files, write a lean main file and move bulky content into separate files.`);
76
+ this.name = "OutputTruncationError";
77
+ this.stopReason = stopReason;
78
+ this.toolCallNames = toolCallNames;
79
+ }
80
+ };
81
+ function collectToolCallNames(message) {
82
+ const m = message;
83
+ const names = [
84
+ ...m.tool_calls ?? [],
85
+ ...m.tool_call_chunks ?? [],
86
+ ...m.invalid_tool_calls ?? []
87
+ ].map((tc) => tc.name).filter((name) => typeof name === "string" && name !== "");
88
+ return [...new Set(names)];
89
+ }
90
+ /**
91
+ * Throws {@link OutputTruncationError} when `message` was truncated by the
92
+ * output token limit AND still carries a tool call. For providers that stream
93
+ * tool arguments incrementally (Anthropic, Bedrock, OpenAI), a tool call
94
+ * emitted under truncation has incomplete arguments — the tool-use block is the
95
+ * last thing the model streams — so letting it through makes the agent loop on a
96
+ * malformed call. No-ops for normal completions, truncated plain-text turns, and
97
+ * providers that deliver complete tool calls atomically (Google/Vertex), where
98
+ * `MAX_TOKENS` does not imply the arguments were cut off.
99
+ */
100
+ function assertNotTruncatedToolCall(message, provider) {
101
+ if (message == null) return;
102
+ if (provider != null && ATOMIC_TOOL_CALL_ARG_PROVIDERS.has(provider)) return;
103
+ const stopReason = getTruncationStopReason(message);
104
+ if (stopReason == null || !hasOpenToolCall(message)) return;
105
+ throw new OutputTruncationError(stopReason, collectToolCallNames(message));
106
+ }
107
+ //#endregion
108
+ exports.assertNotTruncatedToolCall = assertNotTruncatedToolCall;
109
+
110
+ //# sourceMappingURL=truncation.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truncation.cjs","names":[],"sources":["../../../src/llm/truncation.ts"],"sourcesContent":["import type { AIMessageChunk, BaseMessage } from '@langchain/core/messages';\nimport { Providers } from '@/common';\n\n/**\n * Providers whose adapters deliver tool calls as complete, atomic objects\n * rather than streamed argument deltas. Google/Vertex (GenAI) emit each\n * `functionCall` whole and seal it on arrival, so a `MAX_TOKENS` finish does\n * NOT imply the arguments were cut off — the truncation guard must skip them.\n */\nconst ATOMIC_TOOL_CALL_ARG_PROVIDERS = new Set<Providers>([\n Providers.GOOGLE,\n Providers.VERTEXAI,\n]);\n\n/**\n * Normalized truncation reasons across providers. Providers disagree on the\n * key and the value: Anthropic/Bedrock use `max_tokens`, OpenAI uses `length`,\n * Google uses `MAX_TOKENS`.\n */\nexport type TruncationStopReason = 'max_tokens' | 'length';\n\nconst MAX_TOKEN_VALUES = new Set([\n 'max_tokens',\n 'max_token',\n 'maxtokens',\n 'max_output_tokens',\n]);\nconst LENGTH_VALUES = new Set(['length']);\n\nfunction normalizeStopValue(value: unknown): TruncationStopReason | null {\n if (typeof value !== 'string') {\n return null;\n }\n const normalized = value.trim().toLowerCase();\n if (MAX_TOKEN_VALUES.has(normalized)) {\n return 'max_tokens';\n }\n if (LENGTH_VALUES.has(normalized)) {\n return 'length';\n }\n return null;\n}\n\n/**\n * Reads the truncation stop reason off a message, covering every provider\n * shape we stream:\n * - `response_metadata.stopReason` (Bedrock Converse, non-streaming)\n * - `response_metadata.messageStop.stopReason` (Bedrock Converse streaming)\n * - `response_metadata.stop_reason` (Anthropic, non-streaming)\n * - `additional_kwargs.stop_reason` (Anthropic streaming `message_delta`)\n * - `response_metadata.finish_reason` (OpenAI chat-completions / compatible)\n * - `response_metadata.incomplete_details.reason` (OpenAI Responses API)\n * - `response_metadata.finishReason` (Google)\n *\n * Returns the normalized reason when the model stopped because it hit the\n * output token ceiling, otherwise `null`.\n */\nexport function getTruncationStopReason(\n message:\n | Pick<BaseMessage, 'response_metadata' | 'additional_kwargs'>\n | undefined\n | null\n): TruncationStopReason | null {\n const meta = message?.response_metadata as\n | Record<string, unknown>\n | undefined;\n const additionalKwargs = message?.additional_kwargs as\n | Record<string, unknown>\n | undefined;\n if (meta == null && additionalKwargs == null) {\n return null;\n }\n\n const messageStop = meta?.messageStop as { stopReason?: unknown } | undefined;\n const incompleteDetails = meta?.incomplete_details as\n | { reason?: unknown }\n | undefined;\n const candidates: unknown[] = [\n meta?.stopReason,\n meta?.stop_reason,\n meta?.finish_reason,\n meta?.finishReason,\n messageStop?.stopReason,\n incompleteDetails?.reason,\n additionalKwargs?.stop_reason,\n ];\n\n for (const candidate of candidates) {\n const normalized = normalizeStopValue(candidate);\n if (normalized != null) {\n return normalized;\n }\n }\n return null;\n}\n\nfunction hasOpenToolCall(message: AIMessageChunk | BaseMessage): boolean {\n const m = message as AIMessageChunk & {\n tool_calls?: unknown[];\n tool_call_chunks?: unknown[];\n invalid_tool_calls?: unknown[];\n };\n return (\n (m.tool_calls?.length ?? 0) > 0 ||\n (m.tool_call_chunks?.length ?? 0) > 0 ||\n (m.invalid_tool_calls?.length ?? 0) > 0\n );\n}\n\n/**\n * Error raised when a model turn is cut off by the output token limit while it\n * was still emitting a tool call. The arguments are necessarily incomplete, so\n * executing or re-prompting the call would loop on a malformed request. Failing\n * fast with this surfaces an actionable message to the caller instead.\n */\nexport class OutputTruncationError extends Error {\n readonly stopReason: TruncationStopReason;\n readonly toolCallNames: string[];\n\n constructor(stopReason: TruncationStopReason, toolCallNames: string[]) {\n const named =\n toolCallNames.length > 0\n ? ` (tool call: ${toolCallNames.join(', ')})`\n : '';\n super(\n 'The model response was truncated at the maximum output token limit before ' +\n `the tool call arguments were complete${named}. Increase the model's max ` +\n 'output tokens, or have the model produce smaller arguments — for large ' +\n 'files, write a lean main file and move bulky content into separate files.'\n );\n this.name = 'OutputTruncationError';\n this.stopReason = stopReason;\n this.toolCallNames = toolCallNames;\n }\n}\n\nfunction collectToolCallNames(message: AIMessageChunk | BaseMessage): string[] {\n const m = message as AIMessageChunk & {\n tool_calls?: Array<{ name?: string }>;\n tool_call_chunks?: Array<{ name?: string }>;\n invalid_tool_calls?: Array<{ name?: string }>;\n };\n const names = [\n ...(m.tool_calls ?? []),\n ...(m.tool_call_chunks ?? []),\n ...(m.invalid_tool_calls ?? []),\n ]\n .map((tc) => tc.name)\n .filter((name): name is string => typeof name === 'string' && name !== '');\n return [...new Set(names)];\n}\n\n/**\n * Throws {@link OutputTruncationError} when `message` was truncated by the\n * output token limit AND still carries a tool call. For providers that stream\n * tool arguments incrementally (Anthropic, Bedrock, OpenAI), a tool call\n * emitted under truncation has incomplete arguments — the tool-use block is the\n * last thing the model streams — so letting it through makes the agent loop on a\n * malformed call. No-ops for normal completions, truncated plain-text turns, and\n * providers that deliver complete tool calls atomically (Google/Vertex), where\n * `MAX_TOKENS` does not imply the arguments were cut off.\n */\nexport function assertNotTruncatedToolCall(\n message: AIMessageChunk | BaseMessage | undefined | null,\n provider?: Providers\n): void {\n if (message == null) {\n return;\n }\n if (provider != null && ATOMIC_TOOL_CALL_ARG_PROVIDERS.has(provider)) {\n return;\n }\n const stopReason = getTruncationStopReason(message);\n if (stopReason == null || !hasOpenToolCall(message)) {\n return;\n }\n throw new OutputTruncationError(stopReason, collectToolCallNames(message));\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,iCAAiC,IAAI,IAAe,CAAA,UAAA,UAG1D,CAAC;AASD,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,gBAAgB,IAAI,IAAI,CAAC,QAAQ,CAAC;AAExC,SAAS,mBAAmB,OAA6C;CACvE,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,MAAM,aAAa,MAAM,KAAK,CAAC,CAAC,YAAY;CAC5C,IAAI,iBAAiB,IAAI,UAAU,GACjC,OAAO;CAET,IAAI,cAAc,IAAI,UAAU,GAC9B,OAAO;CAET,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,wBACd,SAI6B;CAC7B,MAAM,OAAO,SAAS;CAGtB,MAAM,mBAAmB,SAAS;CAGlC,IAAI,QAAQ,QAAQ,oBAAoB,MACtC,OAAO;CAGT,MAAM,cAAc,MAAM;CAC1B,MAAM,oBAAoB,MAAM;CAGhC,MAAM,aAAwB;EAC5B,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,aAAa;EACb,mBAAmB;EACnB,kBAAkB;CACpB;CAEA,KAAK,MAAM,aAAa,YAAY;EAClC,MAAM,aAAa,mBAAmB,SAAS;EAC/C,IAAI,cAAc,MAChB,OAAO;CAEX;CACA,OAAO;AACT;AAEA,SAAS,gBAAgB,SAAgD;CACvE,MAAM,IAAI;CAKV,QACG,EAAE,YAAY,UAAU,KAAK,MAC7B,EAAE,kBAAkB,UAAU,KAAK,MACnC,EAAE,oBAAoB,UAAU,KAAK;AAE1C;;;;;;;AAQA,IAAa,wBAAb,cAA2C,MAAM;CAC/C;CACA;CAEA,YAAY,YAAkC,eAAyB;EACrE,MAAM,QACJ,cAAc,SAAS,IACnB,gBAAgB,cAAc,KAAK,IAAI,EAAE,KACzC;EACN,MACE,kHAC0C,MAAM,4KAGlD;EACA,KAAK,OAAO;EACZ,KAAK,aAAa;EAClB,KAAK,gBAAgB;CACvB;AACF;AAEA,SAAS,qBAAqB,SAAiD;CAC7E,MAAM,IAAI;CAKV,MAAM,QAAQ;EACZ,GAAI,EAAE,cAAc,CAAC;EACrB,GAAI,EAAE,oBAAoB,CAAC;EAC3B,GAAI,EAAE,sBAAsB,CAAC;CAC/B,CAAC,CACE,KAAK,OAAO,GAAG,IAAI,CAAC,CACpB,QAAQ,SAAyB,OAAO,SAAS,YAAY,SAAS,EAAE;CAC3E,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC;AAC3B;;;;;;;;;;;AAYA,SAAgB,2BACd,SACA,UACM;CACN,IAAI,WAAW,MACb;CAEF,IAAI,YAAY,QAAQ,+BAA+B,IAAI,QAAQ,GACjE;CAEF,MAAM,aAAa,wBAAwB,OAAO;CAClD,IAAI,cAAc,QAAQ,CAAC,gBAAgB,OAAO,GAChD;CAEF,MAAM,IAAI,sBAAsB,YAAY,qBAAqB,OAAO,CAAC;AAC3E"}
@@ -57,6 +57,11 @@ function hasToolOutputReference(value) {
57
57
  if (value !== null && typeof value === "object") return Object.values(value).some((item) => hasToolOutputReference(item));
58
58
  return false;
59
59
  }
60
+ function isEagerExecutionExcludedTool(name, graph) {
61
+ if (name === "") return false;
62
+ const excluded = graph.eagerEventToolExecution?.excludeToolNames;
63
+ return excluded != null && excluded.includes(name);
64
+ }
60
65
  function isDirectGraphTool(name, agentContext) {
61
66
  if (name.startsWith("lc_transfer_to_")) return true;
62
67
  return (agentContext?.graphTools)?.some((tool) => "name" in tool && tool.name === name) === true;
@@ -248,10 +253,10 @@ function createEagerToolExecutionPlan(args) {
248
253
  toolCalls
249
254
  })) return;
250
255
  if (graph.toolOutputReferences?.enabled === true && toolCalls.some((toolCall) => hasToolOutputReference(toolCall.args))) return;
251
- const candidateToolCalls = skipExisting ? toolCalls.filter((toolCall) => {
256
+ const candidateToolCalls = (skipExisting ? toolCalls.filter((toolCall) => {
252
257
  if (toolCall.id == null || toolCall.id === "") return true;
253
258
  return !graph.eagerEventToolExecutions.has(toolCall.id);
254
- }) : toolCalls;
259
+ }) : toolCalls).filter((toolCall) => !isEagerExecutionExcludedTool(toolCall.name, graph));
255
260
  if (candidateToolCalls.length === 0) return [];
256
261
  if (candidateToolCalls.some((toolCall) => toolCall.id == null || toolCall.id === "" || toolCall.name === "" || !skipExisting && graph.eagerEventToolExecutions.has(toolCall.id))) return;
257
262
  const plan = require_eagerEventExecution.buildToolExecutionRequestPlan({