@librechat/agents 3.1.80 → 3.1.82

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.
Files changed (54) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +102 -35
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +13 -0
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/llm/openai/index.cjs +50 -13
  6. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  7. package/dist/cjs/llm/openrouter/index.cjs +17 -7
  8. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  9. package/dist/cjs/llm/openrouter/toolCache.cjs +55 -0
  10. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -0
  11. package/dist/cjs/llm/vertexai/index.cjs +15 -15
  12. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  13. package/dist/cjs/tools/ToolNode.cjs +70 -12
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/esm/agents/AgentContext.mjs +101 -34
  16. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  17. package/dist/esm/graphs/Graph.mjs +13 -0
  18. package/dist/esm/graphs/Graph.mjs.map +1 -1
  19. package/dist/esm/llm/openai/index.mjs +50 -14
  20. package/dist/esm/llm/openai/index.mjs.map +1 -1
  21. package/dist/esm/llm/openrouter/index.mjs +17 -7
  22. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  23. package/dist/esm/llm/openrouter/toolCache.mjs +53 -0
  24. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -0
  25. package/dist/esm/llm/vertexai/index.mjs +15 -16
  26. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  27. package/dist/esm/tools/ToolNode.mjs +70 -12
  28. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  29. package/dist/types/agents/AgentContext.d.ts +6 -1
  30. package/dist/types/llm/openrouter/index.d.ts +1 -0
  31. package/dist/types/llm/openrouter/toolCache.d.ts +2 -0
  32. package/dist/types/llm/vertexai/index.d.ts +18 -1
  33. package/dist/types/tools/ToolNode.d.ts +5 -0
  34. package/dist/types/types/run.d.ts +2 -0
  35. package/package.json +2 -1
  36. package/src/agents/AgentContext.ts +146 -38
  37. package/src/agents/__tests__/AgentContext.test.ts +198 -0
  38. package/src/graphs/Graph.ts +24 -0
  39. package/src/llm/custom-chat-models.smoke.test.ts +76 -0
  40. package/src/llm/openai/deepseek.test.ts +14 -1
  41. package/src/llm/openai/index.ts +38 -12
  42. package/src/llm/openrouter/index.ts +22 -7
  43. package/src/llm/openrouter/reasoning.test.ts +33 -0
  44. package/src/llm/openrouter/toolCache.test.ts +83 -0
  45. package/src/llm/openrouter/toolCache.ts +89 -0
  46. package/src/llm/vertexai/fixThoughtSignatures.test.ts +154 -0
  47. package/src/llm/vertexai/index.ts +16 -22
  48. package/src/messages/cache.test.ts +127 -0
  49. package/src/scripts/openrouter_prompt_cache_live.ts +310 -0
  50. package/src/specs/agent-handoffs.live.test.ts +140 -0
  51. package/src/specs/agent-handoffs.test.ts +266 -2
  52. package/src/specs/openrouter.simple.test.ts +15 -8
  53. package/src/tools/ToolNode.ts +92 -13
  54. package/src/types/run.ts +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../../../src/llm/openrouter/index.ts"],"sourcesContent":["import { ChatOpenAI } from '@/llm/openai';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type {\n ChatOpenAICallOptions,\n OpenAIChatInput,\n OpenAIClient,\n} from '@langchain/openai';\n\nexport type OpenRouterReasoningEffort =\n | 'xhigh'\n | 'high'\n | 'medium'\n | 'low'\n | 'minimal'\n | 'none';\n\nexport interface OpenRouterReasoning {\n effort?: OpenRouterReasoningEffort;\n max_tokens?: number;\n exclude?: boolean;\n enabled?: boolean;\n}\n\nexport interface ChatOpenRouterCallOptions\n extends Omit<ChatOpenAICallOptions, 'reasoning'> {\n /** @deprecated Use `reasoning` object instead */\n include_reasoning?: boolean;\n reasoning?: OpenRouterReasoning;\n modelKwargs?: OpenAIChatInput['modelKwargs'];\n}\n\nexport type ChatOpenRouterInput = Partial<\n ChatOpenRouterCallOptions & OpenAIChatInput\n>;\n\n/** invocationParams return type extended with OpenRouter reasoning */\nexport type OpenRouterInvocationParams = Omit<\n OpenAIClient.Chat.ChatCompletionCreateParams,\n 'messages'\n> & {\n reasoning?: OpenRouterReasoning;\n};\n\ntype InvocationParamsExtra = {\n streaming?: boolean;\n};\n\ninterface OpenRouterReasoningTextDetail {\n type: 'reasoning.text';\n text?: string;\n format?: string;\n index?: number;\n}\n\ninterface OpenRouterReasoningEncryptedDetail {\n type: 'reasoning.encrypted';\n id?: string;\n data?: string;\n format?: string;\n index?: number;\n}\n\ntype OpenRouterReasoningDetail =\n | OpenRouterReasoningTextDetail\n | OpenRouterReasoningEncryptedDetail;\n\nfunction isReasoningTextDetail(\n value: unknown\n): value is OpenRouterReasoningTextDetail {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'type' in value &&\n value.type === 'reasoning.text'\n );\n}\n\nfunction isReasoningEncryptedDetail(\n value: unknown\n): value is OpenRouterReasoningEncryptedDetail {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'type' in value &&\n value.type === 'reasoning.encrypted'\n );\n}\n\nfunction getReasoningDetails(value: unknown): OpenRouterReasoningDetail[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value.filter(\n (detail): detail is OpenRouterReasoningDetail =>\n isReasoningTextDetail(detail) || isReasoningEncryptedDetail(detail)\n );\n}\n\nexport class ChatOpenRouter extends ChatOpenAI {\n private openRouterReasoning?: OpenRouterReasoning;\n /** @deprecated Use `reasoning` object instead */\n private includeReasoning?: boolean;\n\n constructor(_fields: ChatOpenRouterInput) {\n const {\n include_reasoning,\n reasoning: openRouterReasoning,\n modelKwargs = {},\n ...fields\n } = _fields;\n\n // Extract reasoning from modelKwargs if provided there (e.g., from LLMConfig)\n const { reasoning: mkReasoning, ...restModelKwargs } = modelKwargs as {\n reasoning?: OpenRouterReasoning;\n } & Record<string, unknown>;\n\n super({\n ...fields,\n modelKwargs: restModelKwargs,\n includeReasoningDetails: true,\n convertReasoningDetailsToContent: true,\n });\n\n // Merge reasoning config: modelKwargs.reasoning < constructor reasoning\n if (mkReasoning != null || openRouterReasoning != null) {\n this.openRouterReasoning = {\n ...mkReasoning,\n ...openRouterReasoning,\n };\n }\n\n this.includeReasoning = include_reasoning;\n }\n static lc_name(): 'LibreChatOpenRouter' {\n return 'LibreChatOpenRouter';\n }\n\n // @ts-expect-error - OpenRouter reasoning extends OpenAI Reasoning with additional\n // effort levels ('xhigh' | 'none' | 'minimal') not in ReasoningEffort.\n // The parent's generic conditional return type cannot be widened in an override.\n override invocationParams(\n options?: this['ParsedCallOptions'],\n extra?: InvocationParamsExtra\n ): OpenRouterInvocationParams {\n type MutableParams = Omit<\n OpenAIClient.Chat.ChatCompletionCreateParams,\n 'messages'\n > & { reasoning_effort?: string; reasoning?: OpenRouterReasoning };\n\n const optionsWithDefaults = this._combineCallOptions(options);\n const params = (\n this._useResponsesApi(options)\n ? this.responses.invocationParams(optionsWithDefaults)\n : this.completions.invocationParams(optionsWithDefaults, extra)\n ) as MutableParams;\n\n // Remove the OpenAI-native reasoning_effort that the parent sets;\n // OpenRouter uses a `reasoning` object instead\n delete params.reasoning_effort;\n\n // Build the OpenRouter reasoning config\n const reasoning = this.buildOpenRouterReasoning(optionsWithDefaults);\n if (reasoning != null) {\n params.reasoning = reasoning;\n } else {\n delete params.reasoning;\n }\n\n return params;\n }\n\n private buildOpenRouterReasoning(\n options?: this['ParsedCallOptions']\n ): OpenRouterReasoning | undefined {\n let reasoning: OpenRouterReasoning | undefined;\n\n // 1. Instance-level reasoning config (from constructor)\n if (this.openRouterReasoning != null) {\n reasoning = { ...this.openRouterReasoning };\n }\n\n // 2. LangChain-style reasoning params (from parent's `this.reasoning`)\n const lcReasoning = this.getReasoningParams(options);\n if (lcReasoning?.effort != null) {\n reasoning = {\n ...reasoning,\n effort: lcReasoning.effort as OpenRouterReasoningEffort,\n };\n }\n\n // 3. Call-level reasoning override\n const callReasoning = (options as ChatOpenRouterCallOptions | undefined)\n ?.reasoning;\n if (callReasoning != null) {\n reasoning = { ...reasoning, ...callReasoning };\n }\n\n // 4. Legacy include_reasoning backward compatibility\n if (reasoning == null && this.includeReasoning === true) {\n reasoning = { enabled: true };\n }\n\n return reasoning;\n }\n\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'],\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const reasoningTextByIndex = new Map<\n number,\n OpenRouterReasoningTextDetail\n >();\n const reasoningEncryptedById = new Map<\n string,\n OpenRouterReasoningEncryptedDetail\n >();\n\n for await (const generationChunk of super._streamResponseChunks(\n messages,\n options,\n runManager\n )) {\n let currentReasoningText = '';\n const reasoningDetails = getReasoningDetails(\n generationChunk.message.additional_kwargs.reasoning_details\n );\n\n for (const detail of reasoningDetails) {\n if (detail.type === 'reasoning.text') {\n currentReasoningText += detail.text ?? '';\n const index = detail.index ?? 0;\n const existing = reasoningTextByIndex.get(index);\n if (existing != null) {\n existing.text = `${existing.text ?? ''}${detail.text ?? ''}`;\n continue;\n }\n reasoningTextByIndex.set(index, {\n ...detail,\n text: detail.text ?? '',\n });\n continue;\n }\n if (detail.id != null) {\n reasoningEncryptedById.set(detail.id, { ...detail });\n }\n }\n\n if (\n currentReasoningText.length > 0 &&\n generationChunk.message.additional_kwargs.reasoning == null\n ) {\n generationChunk.message.additional_kwargs.reasoning =\n currentReasoningText;\n }\n\n if (generationChunk.generationInfo?.finish_reason != null) {\n const finalReasoningDetails = [\n ...reasoningTextByIndex.values(),\n ...reasoningEncryptedById.values(),\n ];\n if (finalReasoningDetails.length > 0) {\n generationChunk.message.additional_kwargs.reasoning_details =\n finalReasoningDetails;\n } else {\n delete generationChunk.message.additional_kwargs.reasoning_details;\n }\n yield generationChunk;\n continue;\n }\n\n delete generationChunk.message.additional_kwargs.reasoning_details;\n yield generationChunk;\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../../../src/llm/openrouter/index.ts"],"sourcesContent":["import { ChatOpenAI } from '@/llm/openai';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type {\n ChatOpenAICallOptions,\n OpenAIChatInput,\n OpenAIClient,\n} from '@langchain/openai';\n\nexport type OpenRouterReasoningEffort =\n | 'xhigh'\n | 'high'\n | 'medium'\n | 'low'\n | 'minimal'\n | 'none';\n\nexport interface OpenRouterReasoning {\n effort?: OpenRouterReasoningEffort;\n max_tokens?: number;\n exclude?: boolean;\n enabled?: boolean;\n}\n\nexport interface ChatOpenRouterCallOptions\n extends Omit<ChatOpenAICallOptions, 'reasoning'> {\n /** @deprecated Use `reasoning` object instead */\n include_reasoning?: boolean;\n reasoning?: OpenRouterReasoning;\n modelKwargs?: OpenAIChatInput['modelKwargs'];\n promptCache?: boolean;\n}\n\nexport type ChatOpenRouterInput = Partial<\n ChatOpenRouterCallOptions & OpenAIChatInput\n>;\n\n/** invocationParams return type extended with OpenRouter reasoning */\nexport type OpenRouterInvocationParams = Omit<\n OpenAIClient.Chat.ChatCompletionCreateParams,\n 'messages'\n> & {\n reasoning?: OpenRouterReasoning;\n};\n\ntype InvocationParamsExtra = {\n streaming?: boolean;\n};\n\ninterface OpenRouterReasoningTextDetail {\n type: 'reasoning.text';\n text?: string;\n format?: string;\n index?: number;\n}\n\ninterface OpenRouterReasoningEncryptedDetail {\n type: 'reasoning.encrypted';\n id?: string;\n data?: string;\n format?: string;\n index?: number;\n}\n\ntype OpenRouterReasoningDetail =\n | OpenRouterReasoningTextDetail\n | OpenRouterReasoningEncryptedDetail;\n\nfunction isReasoningTextDetail(\n value: unknown\n): value is OpenRouterReasoningTextDetail {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'type' in value &&\n value.type === 'reasoning.text'\n );\n}\n\nfunction isReasoningEncryptedDetail(\n value: unknown\n): value is OpenRouterReasoningEncryptedDetail {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'type' in value &&\n value.type === 'reasoning.encrypted'\n );\n}\n\nfunction getReasoningDetails(value: unknown): OpenRouterReasoningDetail[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value.filter(\n (detail): detail is OpenRouterReasoningDetail =>\n isReasoningTextDetail(detail) || isReasoningEncryptedDetail(detail)\n );\n}\n\nexport class ChatOpenRouter extends ChatOpenAI {\n private openRouterReasoning?: OpenRouterReasoning;\n /** @deprecated Use `reasoning` object instead */\n private includeReasoning?: boolean;\n\n constructor(_fields: ChatOpenRouterInput) {\n const fieldsWithoutPromptCache: ChatOpenRouterInput = { ..._fields };\n delete fieldsWithoutPromptCache.promptCache;\n\n const {\n include_reasoning,\n reasoning: openRouterReasoning,\n modelKwargs = {},\n ...fields\n } = fieldsWithoutPromptCache;\n\n // Extract reasoning from modelKwargs if provided there (e.g., from LLMConfig)\n const { reasoning: mkReasoning, ...restModelKwargs } = modelKwargs as {\n reasoning?: OpenRouterReasoning;\n } & Record<string, unknown>;\n const mergedReasoning =\n mkReasoning != null || openRouterReasoning != null\n ? {\n ...mkReasoning,\n ...openRouterReasoning,\n }\n : undefined;\n const runtimeReasoning =\n mergedReasoning ??\n (include_reasoning === true ? { enabled: true } : undefined);\n const parentModelKwargs =\n runtimeReasoning == null\n ? restModelKwargs\n : { ...restModelKwargs, reasoning: runtimeReasoning };\n\n super({\n ...fields,\n modelKwargs: parentModelKwargs,\n includeReasoningDetails: true,\n convertReasoningDetailsToContent: true,\n });\n\n // Merge reasoning config: modelKwargs.reasoning < constructor reasoning\n if (mergedReasoning != null) {\n this.openRouterReasoning = mergedReasoning;\n }\n\n this.includeReasoning = include_reasoning;\n }\n static lc_name(): 'LibreChatOpenRouter' {\n return 'LibreChatOpenRouter';\n }\n\n // @ts-expect-error - OpenRouter reasoning extends OpenAI Reasoning with additional\n // effort levels ('xhigh' | 'none' | 'minimal') not in ReasoningEffort.\n // The parent's generic conditional return type cannot be widened in an override.\n override invocationParams(\n options?: this['ParsedCallOptions'],\n extra?: InvocationParamsExtra\n ): OpenRouterInvocationParams {\n type MutableParams = Omit<\n OpenAIClient.Chat.ChatCompletionCreateParams,\n 'messages'\n > & { reasoning_effort?: string; reasoning?: OpenRouterReasoning };\n\n const optionsWithDefaults = this._combineCallOptions(options);\n const params = (\n this._useResponsesApi(options)\n ? this.responses.invocationParams(optionsWithDefaults)\n : this.completions.invocationParams(optionsWithDefaults, extra)\n ) as MutableParams;\n\n // Remove the OpenAI-native reasoning_effort that the parent sets;\n // OpenRouter uses a `reasoning` object instead\n delete params.reasoning_effort;\n\n // Build the OpenRouter reasoning config\n const reasoning = this.buildOpenRouterReasoning(optionsWithDefaults);\n if (reasoning != null) {\n params.reasoning = reasoning;\n } else {\n delete params.reasoning;\n }\n\n return params;\n }\n\n private buildOpenRouterReasoning(\n options?: this['ParsedCallOptions']\n ): OpenRouterReasoning | undefined {\n let reasoning: OpenRouterReasoning | undefined;\n\n // 1. Instance-level reasoning config (from constructor)\n if (this.openRouterReasoning != null) {\n reasoning = { ...this.openRouterReasoning };\n }\n\n // 2. LangChain-style reasoning params (from parent's `this.reasoning`)\n const lcReasoning = this.getReasoningParams(options);\n if (lcReasoning?.effort != null) {\n reasoning = {\n ...reasoning,\n effort: lcReasoning.effort as OpenRouterReasoningEffort,\n };\n }\n\n // 3. Call-level reasoning override\n const callReasoning = (options as ChatOpenRouterCallOptions | undefined)\n ?.reasoning;\n if (callReasoning != null) {\n reasoning = { ...reasoning, ...callReasoning };\n }\n\n // 4. Legacy include_reasoning backward compatibility\n if (reasoning == null && this.includeReasoning === true) {\n reasoning = { enabled: true };\n }\n\n return reasoning;\n }\n\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'],\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const reasoningTextByIndex = new Map<\n number,\n OpenRouterReasoningTextDetail\n >();\n const reasoningEncryptedById = new Map<\n string,\n OpenRouterReasoningEncryptedDetail\n >();\n\n for await (const generationChunk of super._streamResponseChunks(\n messages,\n options,\n runManager\n )) {\n let currentReasoningText = '';\n const reasoningDetails = getReasoningDetails(\n generationChunk.message.additional_kwargs.reasoning_details\n );\n\n for (const detail of reasoningDetails) {\n if (detail.type === 'reasoning.text') {\n currentReasoningText += detail.text ?? '';\n const index = detail.index ?? 0;\n const existing = reasoningTextByIndex.get(index);\n if (existing != null) {\n existing.text = `${existing.text ?? ''}${detail.text ?? ''}`;\n continue;\n }\n reasoningTextByIndex.set(index, {\n ...detail,\n text: detail.text ?? '',\n });\n continue;\n }\n if (detail.id != null) {\n reasoningEncryptedById.set(detail.id, { ...detail });\n }\n }\n\n if (\n currentReasoningText.length > 0 &&\n generationChunk.message.additional_kwargs.reasoning == null\n ) {\n generationChunk.message.additional_kwargs.reasoning =\n currentReasoningText;\n }\n\n if (generationChunk.generationInfo?.finish_reason != null) {\n const finalReasoningDetails = [\n ...reasoningTextByIndex.values(),\n ...reasoningEncryptedById.values(),\n ];\n if (finalReasoningDetails.length > 0) {\n generationChunk.message.additional_kwargs.reasoning_details =\n finalReasoningDetails;\n } else {\n delete generationChunk.message.additional_kwargs.reasoning_details;\n }\n yield generationChunk;\n continue;\n }\n\n delete generationChunk.message.additional_kwargs.reasoning_details;\n yield generationChunk;\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { _convertToOpenAITool } from '../openai/index.mjs';
2
+
3
+ const CACHE_CONTROL = { type: 'ephemeral' };
4
+ function getToolName(tool) {
5
+ const candidate = tool;
6
+ if (typeof candidate.name === 'string') {
7
+ return candidate.name;
8
+ }
9
+ if (typeof candidate.function?.name === 'string') {
10
+ return candidate.function.name;
11
+ }
12
+ return undefined;
13
+ }
14
+ function hasDeferredMarker(tool) {
15
+ return tool.defer_loading === true;
16
+ }
17
+ function toOpenRouterTool(tool) {
18
+ const converted = _convertToOpenAITool(tool);
19
+ if (hasDeferredMarker(tool)) {
20
+ return { ...converted, defer_loading: true };
21
+ }
22
+ return converted;
23
+ }
24
+ function markCacheControl(tool) {
25
+ return {
26
+ ...tool,
27
+ cache_control: CACHE_CONTROL,
28
+ };
29
+ }
30
+ function partitionAndMarkOpenRouterToolCache(tools, isDeferred) {
31
+ if (tools == null || tools.length === 0) {
32
+ return tools;
33
+ }
34
+ const staticTools = [];
35
+ const deferredTools = [];
36
+ for (const tool of tools) {
37
+ const converted = toOpenRouterTool(tool);
38
+ const name = getToolName(converted) ?? getToolName(tool);
39
+ if (name != null && isDeferred(name)) {
40
+ deferredTools.push(converted);
41
+ continue;
42
+ }
43
+ staticTools.push(converted);
44
+ }
45
+ if (staticTools.length === 0) {
46
+ return [...deferredTools];
47
+ }
48
+ staticTools[staticTools.length - 1] = markCacheControl(staticTools[staticTools.length - 1]);
49
+ return [...staticTools, ...deferredTools];
50
+ }
51
+
52
+ export { partitionAndMarkOpenRouterToolCache };
53
+ //# sourceMappingURL=toolCache.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCache.mjs","sources":["../../../../src/llm/openrouter/toolCache.ts"],"sourcesContent":["import type { BindToolsInput } from '@langchain/core/language_models/chat_models';\nimport type { OpenAIClient } from '@langchain/openai';\nimport type { GraphTools } from '@/types';\nimport { _convertToOpenAITool } from '@/llm/openai';\n\nconst CACHE_CONTROL = { type: 'ephemeral' as const };\n\ntype OpenRouterToolWithCacheControl = OpenAIClient.ChatCompletionTool & {\n cache_control?: typeof CACHE_CONTROL;\n defer_loading?: boolean;\n};\n\ntype ToolNameCandidate = {\n name?: unknown;\n function?: {\n name?: unknown;\n };\n defer_loading?: unknown;\n};\n\nfunction getToolName(tool: unknown): string | undefined {\n const candidate = tool as ToolNameCandidate;\n if (typeof candidate.name === 'string') {\n return candidate.name;\n }\n if (typeof candidate.function?.name === 'string') {\n return candidate.function.name;\n }\n return undefined;\n}\n\nfunction hasDeferredMarker(tool: unknown): boolean {\n return (tool as ToolNameCandidate).defer_loading === true;\n}\n\nfunction toOpenRouterTool(tool: unknown): OpenRouterToolWithCacheControl {\n const converted = _convertToOpenAITool(\n tool as BindToolsInput\n ) as OpenRouterToolWithCacheControl;\n\n if (hasDeferredMarker(tool)) {\n return { ...converted, defer_loading: true };\n }\n\n return converted;\n}\n\nfunction markCacheControl(\n tool: OpenRouterToolWithCacheControl\n): OpenRouterToolWithCacheControl {\n return {\n ...tool,\n cache_control: CACHE_CONTROL,\n };\n}\n\nexport function partitionAndMarkOpenRouterToolCache(\n tools: GraphTools | undefined,\n isDeferred: (toolName: string) => boolean\n): GraphTools | undefined {\n if (tools == null || tools.length === 0) {\n return tools;\n }\n\n const staticTools: OpenRouterToolWithCacheControl[] = [];\n const deferredTools: OpenRouterToolWithCacheControl[] = [];\n\n for (const tool of tools as readonly unknown[]) {\n const converted = toOpenRouterTool(tool);\n const name = getToolName(converted) ?? getToolName(tool);\n\n if (name != null && isDeferred(name)) {\n deferredTools.push(converted);\n continue;\n }\n\n staticTools.push(converted);\n }\n\n if (staticTools.length === 0) {\n return [...deferredTools] as GraphTools;\n }\n\n staticTools[staticTools.length - 1] = markCacheControl(\n staticTools[staticTools.length - 1]\n );\n\n return [...staticTools, ...deferredTools] as GraphTools;\n}\n"],"names":[],"mappings":";;AAKA,MAAM,aAAa,GAAG,EAAE,IAAI,EAAE,WAAoB,EAAE;AAepD,SAAS,WAAW,CAAC,IAAa,EAAA;IAChC,MAAM,SAAS,GAAG,IAAyB;AAC3C,IAAA,IAAI,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE;QACtC,OAAO,SAAS,CAAC,IAAI;IACvB;IACA,IAAI,OAAO,SAAS,CAAC,QAAQ,EAAE,IAAI,KAAK,QAAQ,EAAE;AAChD,QAAA,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI;IAChC;AACA,IAAA,OAAO,SAAS;AAClB;AAEA,SAAS,iBAAiB,CAAC,IAAa,EAAA;AACtC,IAAA,OAAQ,IAA0B,CAAC,aAAa,KAAK,IAAI;AAC3D;AAEA,SAAS,gBAAgB,CAAC,IAAa,EAAA;AACrC,IAAA,MAAM,SAAS,GAAG,oBAAoB,CACpC,IAAsB,CACW;AAEnC,IAAA,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE;QAC3B,OAAO,EAAE,GAAG,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE;IAC9C;AAEA,IAAA,OAAO,SAAS;AAClB;AAEA,SAAS,gBAAgB,CACvB,IAAoC,EAAA;IAEpC,OAAO;AACL,QAAA,GAAG,IAAI;AACP,QAAA,aAAa,EAAE,aAAa;KAC7B;AACH;AAEM,SAAU,mCAAmC,CACjD,KAA6B,EAC7B,UAAyC,EAAA;IAEzC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;AACvC,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,WAAW,GAAqC,EAAE;IACxD,MAAM,aAAa,GAAqC,EAAE;AAE1D,IAAA,KAAK,MAAM,IAAI,IAAI,KAA2B,EAAE;AAC9C,QAAA,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC;QACxC,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC;QAExD,IAAI,IAAI,IAAI,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE;AACpC,YAAA,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;YAC7B;QACF;AAEA,QAAA,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC;IAC7B;AAEA,IAAA,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;AAC5B,QAAA,OAAO,CAAC,GAAG,aAAa,CAAe;IACzC;AAEA,IAAA,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,gBAAgB,CACpD,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CACpC;AAED,IAAA,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,aAAa,CAAe;AACzD;;;;"}
@@ -48,34 +48,33 @@ function repairStreamUsageMetadata(current, generationInfoUsage) {
48
48
  * - The signature for a functionCall part is an empty string
49
49
  *
50
50
  * This function correlates each "model" content block in the formatted request
51
- * back to its originating AI message, then re-attaches non-empty signatures
52
- * that the library failed to apply.
51
+ * back to its originating AI message by *position*, then re-attaches non-empty
52
+ * signatures that the library failed to apply. AI messages without signatures
53
+ * still consume their slot — filtering them out shifted later messages onto
54
+ * the wrong content block and dropped real signatures on the floor.
53
55
  */
54
56
  function fixThoughtSignatures(contents, input) {
55
- // Collect AI messages that have signatures, in order
56
- const aiMessages = input.filter((msg) => isAIMessage(msg) &&
57
- Array.isArray(msg.additional_kwargs?.signatures) &&
58
- msg.additional_kwargs.signatures.length > 0);
59
- // Collect "model" content blocks from the formatted request, in order
57
+ // All AI messages, in order non-signature ones still consume positional
58
+ // slots so later messages line up with their model content blocks.
59
+ const aiMessages = input.filter(isAIMessage);
60
60
  const modelContents = contents.filter((c) => c.role === 'model');
61
- // They should correspond 1:1 in order (both derived from the same input sequence)
62
61
  const count = Math.min(aiMessages.length, modelContents.length);
63
62
  for (let i = 0; i < count; i++) {
64
- const msg = aiMessages[i];
63
+ const signatures = aiMessages[i].additional_kwargs
64
+ ?.signatures;
65
+ if (!Array.isArray(signatures) || signatures.length === 0)
66
+ continue;
65
67
  const content = modelContents[i];
66
- const signatures = msg.additional_kwargs?.signatures;
67
- // Collect non-empty signatures that aren't already attached to any part
68
68
  const attachedSignatures = new Set(content.parts
69
69
  .map((p) => p.thoughtSignature)
70
70
  .filter((s) => s != null && s !== ''));
71
- const availableSignatures = signatures?.filter((s) => s != null && s !== '' && !attachedSignatures.has(s));
72
- // Assign available signatures to functionCall parts missing one, in order
71
+ const availableSignatures = signatures.filter((s) => s != null && s !== '' && !attachedSignatures.has(s));
73
72
  let sigIdx = 0;
74
73
  for (const part of content.parts) {
75
74
  if ('functionCall' in part &&
76
75
  (part.thoughtSignature == null || part.thoughtSignature === '') &&
77
- sigIdx < (availableSignatures?.length ?? 0)) {
78
- part.thoughtSignature = availableSignatures?.[sigIdx];
76
+ sigIdx < availableSignatures.length) {
77
+ part.thoughtSignature = availableSignatures[sigIdx];
79
78
  sigIdx++;
80
79
  }
81
80
  }
@@ -454,5 +453,5 @@ class ChatVertexAI extends ChatGoogle {
454
453
  }
455
454
  }
456
455
 
457
- export { ChatVertexAI, repairStreamUsageMetadata };
456
+ export { ChatVertexAI, fixThoughtSignatures, repairStreamUsageMetadata };
458
457
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../../../src/llm/vertexai/index.ts"],"sourcesContent":["import { ChatGoogle } from '@langchain/google-gauth';\nimport { ChatConnection } from '@langchain/google-common';\nimport type {\n GeminiContent,\n GeminiRequest,\n GoogleAIModelRequestParams,\n GoogleAbstractedClient,\n} from '@langchain/google-common';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { BaseMessage, UsageMetadata } from '@langchain/core/messages';\nimport { AIMessageChunk, isAIMessage } from '@langchain/core/messages';\nimport type { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { GoogleThinkingConfig, VertexAIClientOptions } from '@/types';\n\n/**\n * `@langchain/google-common`'s `_streamResponseChunks` emits usage on TWO\n * different paths within the same stream:\n *\n * - Streaming chunks set `chunk.generationInfo.usage_metadata` via\n * `responseToUsageMetadata`, which correctly sums\n * `candidatesTokenCount + thoughtsTokenCount` and includes\n * `output_token_details.reasoning`.\n * - The trailing fallback chunk (emitted after the API stream exhausts)\n * attaches its own `chunk.message.usage_metadata` built inline as\n * `output_tokens = candidatesTokenCount` only — dropping\n * `thoughtsTokenCount` and `output_token_details` entirely.\n *\n * After `AIMessageChunk.concat`, only `message.usage_metadata` survives —\n * which is the buggy fallback value. This breaks the documented\n * `total_tokens === input_tokens + output_tokens` invariant and silently\n * undercharges thinking models for reasoning tokens.\n *\n * The repair: track the last `generationInfo.usage_metadata` we see, and\n * when the fallback chunk arrives with its buggy `message.usage_metadata`,\n * replace it with the tracked good value. `CustomChatGoogleGenerativeAI`\n * solves the same problem for the Google API path differently — by\n * overriding `_convertToUsageMetadata`.\n */\nexport function repairStreamUsageMetadata(\n current: UsageMetadata | undefined,\n generationInfoUsage: UsageMetadata | undefined\n): UsageMetadata | undefined {\n if (!current) return current;\n if (!generationInfoUsage) return current;\n if (generationInfoUsage.total_tokens !== current.total_tokens) return current;\n if (generationInfoUsage.output_tokens <= current.output_tokens)\n return current;\n return generationInfoUsage;\n}\n\ntype AdditionalKwargs =\n | undefined\n | (BaseMessage['additional_kwargs'] & {\n signatures?: Array<string | undefined>;\n });\n\n/**\n * Fixes thought signatures on functionCall parts in the formatted Gemini request.\n *\n * `@langchain/google-common` stores signatures as a flat array in\n * `additional_kwargs.signatures` (one per response part) and re-attaches them\n * by index only when `signatures.length === parts.length`. This fails when:\n * - The API omits a signature (length mismatch)\n * - Streaming chunks merge with different part counts\n * - The signature for a functionCall part is an empty string\n *\n * This function correlates each \"model\" content block in the formatted request\n * back to its originating AI message, then re-attaches non-empty signatures\n * that the library failed to apply.\n */\nfunction fixThoughtSignatures(\n contents: GeminiContent[],\n input: BaseMessage[]\n): void {\n // Collect AI messages that have signatures, in order\n const aiMessages = input.filter(\n (msg) =>\n isAIMessage(msg) &&\n Array.isArray((msg.additional_kwargs as AdditionalKwargs)?.signatures) &&\n (msg.additional_kwargs.signatures as string[]).length > 0\n );\n\n // Collect \"model\" content blocks from the formatted request, in order\n const modelContents = contents.filter((c) => c.role === 'model');\n\n // They should correspond 1:1 in order (both derived from the same input sequence)\n const count = Math.min(aiMessages.length, modelContents.length);\n for (let i = 0; i < count; i++) {\n const msg = aiMessages[i];\n const content = modelContents[i];\n const signatures = (msg.additional_kwargs as AdditionalKwargs)?.signatures;\n\n // Collect non-empty signatures that aren't already attached to any part\n const attachedSignatures = new Set(\n content.parts\n .map((p) => p.thoughtSignature)\n .filter((s): s is string => s != null && s !== '')\n );\n const availableSignatures = signatures?.filter(\n (s) => s != null && s !== '' && !attachedSignatures.has(s)\n );\n\n // Assign available signatures to functionCall parts missing one, in order\n let sigIdx = 0;\n for (const part of content.parts) {\n if (\n 'functionCall' in part &&\n (part.thoughtSignature == null || part.thoughtSignature === '') &&\n sigIdx < (availableSignatures?.length ?? 0)\n ) {\n part.thoughtSignature = availableSignatures?.[sigIdx];\n sigIdx++;\n }\n }\n }\n}\n\nclass CustomChatConnection extends ChatConnection<VertexAIClientOptions> {\n thinkingConfig?: GoogleThinkingConfig;\n\n async formatData(\n input: BaseMessage[],\n parameters: GoogleAIModelRequestParams\n ): Promise<unknown> {\n const formattedData = (await super.formatData(\n input,\n parameters\n )) as GeminiRequest;\n if (formattedData.generationConfig?.thinkingConfig?.thinkingBudget === -1) {\n // -1 means \"let the model decide\" - delete the property so the API doesn't receive an invalid value\n if (\n formattedData.generationConfig.thinkingConfig.includeThoughts === false\n ) {\n formattedData.generationConfig.thinkingConfig.includeThoughts = true;\n }\n delete formattedData.generationConfig.thinkingConfig.thinkingBudget;\n }\n if (this.thinkingConfig?.thinkingLevel != null) {\n formattedData.generationConfig ??= {};\n // thinkingLevel and thinkingBudget cannot coexist — the API rejects the request.\n // Remove thinkingBudget when thinkingLevel is set.\n const { thinkingBudget: _, ...existingThinkingConfig } =\n (formattedData.generationConfig.thinkingConfig as\n | Record<string, unknown>\n | undefined) ?? {};\n (\n formattedData.generationConfig as Record<string, unknown>\n ).thinkingConfig = {\n ...existingThinkingConfig,\n thinkingLevel: this.thinkingConfig.thinkingLevel,\n ...(this.thinkingConfig.includeThoughts != null && {\n includeThoughts: this.thinkingConfig.includeThoughts,\n }),\n };\n }\n if (formattedData.contents) {\n fixThoughtSignatures(formattedData.contents, input);\n // gemini-3.1+ models reject role=\"function\"; convert to role=\"user\"\n for (const content of formattedData.contents) {\n if (content.role === 'function') {\n (content as { role: string }).role = 'user';\n }\n }\n }\n return formattedData;\n }\n}\n\n/**\n * Integration with Google Vertex AI chat models.\n *\n * Setup:\n * Install `@langchain/google-vertexai` and set your stringified\n * Vertex AI credentials as an environment variable named `GOOGLE_APPLICATION_CREDENTIALS`.\n *\n * ```bash\n * npm install @langchain/google-vertexai\n * export GOOGLE_APPLICATION_CREDENTIALS=\"path/to/credentials\"\n * ```\n *\n * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_google_vertexai.index.ChatVertexAI.html#constructor.new_ChatVertexAI)\n *\n * ## [Runtime args](https://api.js.langchain.com/interfaces/langchain_google_common_types.GoogleAIBaseLanguageModelCallOptions.html)\n *\n * Runtime args can be passed as the second argument to any of the base runnable methods `.invoke`. `.stream`, `.batch`, etc.\n * They can also be passed via `.withConfig`, or the second arg in `.bindTools`, like shown in the examples below:\n *\n * ```typescript\n * // When calling `.withConfig`, call options should be passed via the first argument\n * const llmWithArgsBound = llm.withConfig({\n * stop: [\"\\n\"],\n * tools: [...],\n * });\n *\n * // When calling `.bindTools`, call options should be passed via the second argument\n * const llmWithTools = llm.bindTools(\n * [...],\n * {\n * tool_choice: \"auto\",\n * }\n * );\n * ```\n *\n * ## Examples\n *\n * <details open>\n * <summary><strong>Instantiate</strong></summary>\n *\n * ```typescript\n * import { ChatVertexAI } from '@langchain/google-vertexai';\n *\n * const llm = new ChatVertexAI({\n * model: \"gemini-1.5-pro\",\n * temperature: 0,\n * // other params...\n * });\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Invoking</strong></summary>\n *\n * ```typescript\n * const input = `Translate \"I love programming\" into French.`;\n *\n * // Models also accept a list of chat messages or a formatted prompt\n * const result = await llm.invoke(input);\n * console.log(result);\n * ```\n *\n * ```txt\n * AIMessageChunk {\n * \"content\": \"\\\"J'adore programmer\\\" \\n\\nHere's why this is the best translation:\\n\\n* **J'adore** means \\\"I love\\\" and conveys a strong passion.\\n* **Programmer** is the French verb for \\\"to program.\\\"\\n\\nThis translation is natural and idiomatic in French. \\n\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": [],\n * \"usage_metadata\": {\n * \"input_tokens\": 9,\n * \"output_tokens\": 63,\n * \"total_tokens\": 72\n * }\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Streaming Chunks</strong></summary>\n *\n * ```typescript\n * for await (const chunk of await llm.stream(input)) {\n * console.log(chunk);\n * }\n * ```\n *\n * ```txt\n * AIMessageChunk {\n * \"content\": \"\\\"\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": []\n * }\n * AIMessageChunk {\n * \"content\": \"J'adore programmer\\\" \\n\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": []\n * }\n * AIMessageChunk {\n * \"content\": \"\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": []\n * }\n * AIMessageChunk {\n * \"content\": \"\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {\n * \"finishReason\": \"stop\"\n * },\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": [],\n * \"usage_metadata\": {\n * \"input_tokens\": 9,\n * \"output_tokens\": 8,\n * \"total_tokens\": 17\n * }\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Aggregate Streamed Chunks</strong></summary>\n *\n * ```typescript\n * import { AIMessageChunk } from '@langchain/core/messages';\n * import { concat } from '@langchain/core/utils/stream';\n *\n * const stream = await llm.stream(input);\n * let full: AIMessageChunk | undefined;\n * for await (const chunk of stream) {\n * full = !full ? chunk : concat(full, chunk);\n * }\n * console.log(full);\n * ```\n *\n * ```txt\n * AIMessageChunk {\n * \"content\": \"\\\"J'adore programmer\\\" \\n\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {\n * \"finishReason\": \"stop\"\n * },\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": [],\n * \"usage_metadata\": {\n * \"input_tokens\": 9,\n * \"output_tokens\": 8,\n * \"total_tokens\": 17\n * }\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Bind tools</strong></summary>\n *\n * ```typescript\n * import { z } from 'zod';\n *\n * const GetWeather = {\n * name: \"GetWeather\",\n * description: \"Get the current weather in a given location\",\n * schema: z.object({\n * location: z.string().describe(\"The city and state, e.g. San Francisco, CA\")\n * }),\n * }\n *\n * const GetPopulation = {\n * name: \"GetPopulation\",\n * description: \"Get the current population in a given location\",\n * schema: z.object({\n * location: z.string().describe(\"The city and state, e.g. San Francisco, CA\")\n * }),\n * }\n *\n * const llmWithTools = llm.bindTools([GetWeather, GetPopulation]);\n * const aiMsg = await llmWithTools.invoke(\n * \"Which city is hotter today and which is bigger: LA or NY?\"\n * );\n * console.log(aiMsg.tool_calls);\n * ```\n *\n * ```txt\n * [\n * {\n * name: 'GetPopulation',\n * args: { location: 'New York City, NY' },\n * id: '33c1c1f47e2f492799c77d2800a43912',\n * type: 'tool_call'\n * }\n * ]\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Structured Output</strong></summary>\n *\n * ```typescript\n * import { z } from 'zod';\n *\n * const Joke = z.object({\n * setup: z.string().describe(\"The setup of the joke\"),\n * punchline: z.string().describe(\"The punchline to the joke\"),\n * rating: z.number().optional().describe(\"How funny the joke is, from 1 to 10\")\n * }).describe('Joke to tell user.');\n *\n * const structuredLlm = llm.withStructuredOutput(Joke, { name: \"Joke\" });\n * const jokeResult = await structuredLlm.invoke(\"Tell me a joke about cats\");\n * console.log(jokeResult);\n * ```\n *\n * ```txt\n * {\n * setup: 'What do you call a cat that loves to bowl?',\n * punchline: 'An alley cat!'\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Usage Metadata</strong></summary>\n *\n * ```typescript\n * const aiMsgForMetadata = await llm.invoke(input);\n * console.log(aiMsgForMetadata.usage_metadata);\n * ```\n *\n * ```txt\n * { input_tokens: 9, output_tokens: 8, total_tokens: 17 }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Stream Usage Metadata</strong></summary>\n *\n * ```typescript\n * const streamForMetadata = await llm.stream(\n * input,\n * {\n * streamUsage: true\n * }\n * );\n * let fullForMetadata: AIMessageChunk | undefined;\n * for await (const chunk of streamForMetadata) {\n * fullForMetadata = !fullForMetadata ? chunk : concat(fullForMetadata, chunk);\n * }\n * console.log(fullForMetadata?.usage_metadata);\n * ```\n *\n * ```txt\n * { input_tokens: 9, output_tokens: 8, total_tokens: 17 }\n * ```\n * </details>\n *\n * <br />\n */\nexport class ChatVertexAI extends ChatGoogle {\n lc_namespace = ['langchain', 'chat_models', 'vertexai'];\n dynamicThinkingBudget = false;\n thinkingConfig?: GoogleThinkingConfig;\n\n static lc_name(): 'LibreChatVertexAI' {\n return 'LibreChatVertexAI';\n }\n\n constructor(model: string, fields?: Omit<VertexAIClientOptions, 'model'>);\n constructor(fields?: VertexAIClientOptions);\n constructor(\n modelOrFields?: string | VertexAIClientOptions,\n params?: Omit<VertexAIClientOptions, 'model'>\n ) {\n const fields =\n typeof modelOrFields === 'string'\n ? { ...(params ?? {}), model: modelOrFields }\n : modelOrFields;\n const dynamicThinkingBudget = fields?.thinkingBudget === -1;\n super({\n ...fields,\n platformType: 'gcp',\n });\n this.dynamicThinkingBudget = dynamicThinkingBudget;\n this.thinkingConfig = fields?.thinkingConfig;\n }\n invocationParams(\n options?: this['ParsedCallOptions'] | undefined\n ): GoogleAIModelRequestParams {\n const params = super.invocationParams(options);\n if (this.dynamicThinkingBudget) {\n params.maxReasoningTokens = -1;\n }\n return params;\n }\n async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'],\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n let lastGoodUsage: UsageMetadata | undefined;\n for await (const chunk of super._streamResponseChunks(\n messages,\n options,\n runManager\n )) {\n const genUsage = (\n chunk.generationInfo as { usage_metadata?: UsageMetadata } | undefined\n )?.usage_metadata;\n if (genUsage) {\n lastGoodUsage = genUsage;\n }\n if (chunk.message instanceof AIMessageChunk) {\n const repaired = repairStreamUsageMetadata(\n chunk.message.usage_metadata,\n lastGoodUsage\n );\n if (repaired !== chunk.message.usage_metadata) {\n chunk.message.usage_metadata = repaired;\n }\n }\n yield chunk;\n }\n }\n buildConnection(\n fields: VertexAIClientOptions | undefined,\n client: GoogleAbstractedClient\n ): void {\n // Note: buildConnection is called from super() BEFORE this.thinkingConfig is set,\n // so we must read thinkingConfig from `fields` directly.\n const thinkingConfig = fields?.thinkingConfig ?? this.thinkingConfig;\n\n const connection = new CustomChatConnection(\n { ...fields, ...this },\n this.caller,\n client,\n false\n );\n connection.thinkingConfig = thinkingConfig;\n this.connection = connection;\n\n const streamedConnection = new CustomChatConnection(\n { ...fields, ...this },\n this.caller,\n client,\n true\n );\n streamedConnection.thinkingConfig = thinkingConfig;\n this.streamedConnection = streamedConnection;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../../../src/llm/vertexai/index.ts"],"sourcesContent":["import { ChatGoogle } from '@langchain/google-gauth';\nimport { ChatConnection } from '@langchain/google-common';\nimport type {\n GeminiContent,\n GeminiRequest,\n GoogleAIModelRequestParams,\n GoogleAbstractedClient,\n} from '@langchain/google-common';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { BaseMessage, UsageMetadata } from '@langchain/core/messages';\nimport { AIMessageChunk, isAIMessage } from '@langchain/core/messages';\nimport type { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { GoogleThinkingConfig, VertexAIClientOptions } from '@/types';\n\n/**\n * `@langchain/google-common`'s `_streamResponseChunks` emits usage on TWO\n * different paths within the same stream:\n *\n * - Streaming chunks set `chunk.generationInfo.usage_metadata` via\n * `responseToUsageMetadata`, which correctly sums\n * `candidatesTokenCount + thoughtsTokenCount` and includes\n * `output_token_details.reasoning`.\n * - The trailing fallback chunk (emitted after the API stream exhausts)\n * attaches its own `chunk.message.usage_metadata` built inline as\n * `output_tokens = candidatesTokenCount` only — dropping\n * `thoughtsTokenCount` and `output_token_details` entirely.\n *\n * After `AIMessageChunk.concat`, only `message.usage_metadata` survives —\n * which is the buggy fallback value. This breaks the documented\n * `total_tokens === input_tokens + output_tokens` invariant and silently\n * undercharges thinking models for reasoning tokens.\n *\n * The repair: track the last `generationInfo.usage_metadata` we see, and\n * when the fallback chunk arrives with its buggy `message.usage_metadata`,\n * replace it with the tracked good value. `CustomChatGoogleGenerativeAI`\n * solves the same problem for the Google API path differently — by\n * overriding `_convertToUsageMetadata`.\n */\nexport function repairStreamUsageMetadata(\n current: UsageMetadata | undefined,\n generationInfoUsage: UsageMetadata | undefined\n): UsageMetadata | undefined {\n if (!current) return current;\n if (!generationInfoUsage) return current;\n if (generationInfoUsage.total_tokens !== current.total_tokens) return current;\n if (generationInfoUsage.output_tokens <= current.output_tokens)\n return current;\n return generationInfoUsage;\n}\n\ntype AdditionalKwargs =\n | undefined\n | (BaseMessage['additional_kwargs'] & {\n signatures?: Array<string | undefined>;\n });\n\n/**\n * Fixes thought signatures on functionCall parts in the formatted Gemini request.\n *\n * `@langchain/google-common` stores signatures as a flat array in\n * `additional_kwargs.signatures` (one per response part) and re-attaches them\n * by index only when `signatures.length === parts.length`. This fails when:\n * - The API omits a signature (length mismatch)\n * - Streaming chunks merge with different part counts\n * - The signature for a functionCall part is an empty string\n *\n * This function correlates each \"model\" content block in the formatted request\n * back to its originating AI message by *position*, then re-attaches non-empty\n * signatures that the library failed to apply. AI messages without signatures\n * still consume their slot — filtering them out shifted later messages onto\n * the wrong content block and dropped real signatures on the floor.\n */\nexport function fixThoughtSignatures(\n contents: GeminiContent[],\n input: BaseMessage[]\n): void {\n // All AI messages, in order — non-signature ones still consume positional\n // slots so later messages line up with their model content blocks.\n const aiMessages = input.filter(isAIMessage);\n const modelContents = contents.filter((c) => c.role === 'model');\n\n const count = Math.min(aiMessages.length, modelContents.length);\n for (let i = 0; i < count; i++) {\n const signatures = (aiMessages[i].additional_kwargs as AdditionalKwargs)\n ?.signatures;\n if (!Array.isArray(signatures) || signatures.length === 0) continue;\n\n const content = modelContents[i];\n const attachedSignatures = new Set(\n content.parts\n .map((p) => p.thoughtSignature)\n .filter((s): s is string => s != null && s !== '')\n );\n const availableSignatures = signatures.filter(\n (s): s is string => s != null && s !== '' && !attachedSignatures.has(s)\n );\n\n let sigIdx = 0;\n for (const part of content.parts) {\n if (\n 'functionCall' in part &&\n (part.thoughtSignature == null || part.thoughtSignature === '') &&\n sigIdx < availableSignatures.length\n ) {\n part.thoughtSignature = availableSignatures[sigIdx];\n sigIdx++;\n }\n }\n }\n}\n\nclass CustomChatConnection extends ChatConnection<VertexAIClientOptions> {\n thinkingConfig?: GoogleThinkingConfig;\n\n async formatData(\n input: BaseMessage[],\n parameters: GoogleAIModelRequestParams\n ): Promise<unknown> {\n const formattedData = (await super.formatData(\n input,\n parameters\n )) as GeminiRequest;\n if (formattedData.generationConfig?.thinkingConfig?.thinkingBudget === -1) {\n // -1 means \"let the model decide\" - delete the property so the API doesn't receive an invalid value\n if (\n formattedData.generationConfig.thinkingConfig.includeThoughts === false\n ) {\n formattedData.generationConfig.thinkingConfig.includeThoughts = true;\n }\n delete formattedData.generationConfig.thinkingConfig.thinkingBudget;\n }\n if (this.thinkingConfig?.thinkingLevel != null) {\n formattedData.generationConfig ??= {};\n // thinkingLevel and thinkingBudget cannot coexist — the API rejects the request.\n // Remove thinkingBudget when thinkingLevel is set.\n const { thinkingBudget: _, ...existingThinkingConfig } =\n (formattedData.generationConfig.thinkingConfig as\n | Record<string, unknown>\n | undefined) ?? {};\n (\n formattedData.generationConfig as Record<string, unknown>\n ).thinkingConfig = {\n ...existingThinkingConfig,\n thinkingLevel: this.thinkingConfig.thinkingLevel,\n ...(this.thinkingConfig.includeThoughts != null && {\n includeThoughts: this.thinkingConfig.includeThoughts,\n }),\n };\n }\n if (formattedData.contents) {\n fixThoughtSignatures(formattedData.contents, input);\n // gemini-3.1+ models reject role=\"function\"; convert to role=\"user\"\n for (const content of formattedData.contents) {\n if (content.role === 'function') {\n (content as { role: string }).role = 'user';\n }\n }\n }\n return formattedData;\n }\n}\n\n/**\n * Integration with Google Vertex AI chat models.\n *\n * Setup:\n * Install `@langchain/google-vertexai` and set your stringified\n * Vertex AI credentials as an environment variable named `GOOGLE_APPLICATION_CREDENTIALS`.\n *\n * ```bash\n * npm install @langchain/google-vertexai\n * export GOOGLE_APPLICATION_CREDENTIALS=\"path/to/credentials\"\n * ```\n *\n * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_google_vertexai.index.ChatVertexAI.html#constructor.new_ChatVertexAI)\n *\n * ## [Runtime args](https://api.js.langchain.com/interfaces/langchain_google_common_types.GoogleAIBaseLanguageModelCallOptions.html)\n *\n * Runtime args can be passed as the second argument to any of the base runnable methods `.invoke`. `.stream`, `.batch`, etc.\n * They can also be passed via `.withConfig`, or the second arg in `.bindTools`, like shown in the examples below:\n *\n * ```typescript\n * // When calling `.withConfig`, call options should be passed via the first argument\n * const llmWithArgsBound = llm.withConfig({\n * stop: [\"\\n\"],\n * tools: [...],\n * });\n *\n * // When calling `.bindTools`, call options should be passed via the second argument\n * const llmWithTools = llm.bindTools(\n * [...],\n * {\n * tool_choice: \"auto\",\n * }\n * );\n * ```\n *\n * ## Examples\n *\n * <details open>\n * <summary><strong>Instantiate</strong></summary>\n *\n * ```typescript\n * import { ChatVertexAI } from '@langchain/google-vertexai';\n *\n * const llm = new ChatVertexAI({\n * model: \"gemini-1.5-pro\",\n * temperature: 0,\n * // other params...\n * });\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Invoking</strong></summary>\n *\n * ```typescript\n * const input = `Translate \"I love programming\" into French.`;\n *\n * // Models also accept a list of chat messages or a formatted prompt\n * const result = await llm.invoke(input);\n * console.log(result);\n * ```\n *\n * ```txt\n * AIMessageChunk {\n * \"content\": \"\\\"J'adore programmer\\\" \\n\\nHere's why this is the best translation:\\n\\n* **J'adore** means \\\"I love\\\" and conveys a strong passion.\\n* **Programmer** is the French verb for \\\"to program.\\\"\\n\\nThis translation is natural and idiomatic in French. \\n\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": [],\n * \"usage_metadata\": {\n * \"input_tokens\": 9,\n * \"output_tokens\": 63,\n * \"total_tokens\": 72\n * }\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Streaming Chunks</strong></summary>\n *\n * ```typescript\n * for await (const chunk of await llm.stream(input)) {\n * console.log(chunk);\n * }\n * ```\n *\n * ```txt\n * AIMessageChunk {\n * \"content\": \"\\\"\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": []\n * }\n * AIMessageChunk {\n * \"content\": \"J'adore programmer\\\" \\n\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": []\n * }\n * AIMessageChunk {\n * \"content\": \"\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {},\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": []\n * }\n * AIMessageChunk {\n * \"content\": \"\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {\n * \"finishReason\": \"stop\"\n * },\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": [],\n * \"usage_metadata\": {\n * \"input_tokens\": 9,\n * \"output_tokens\": 8,\n * \"total_tokens\": 17\n * }\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Aggregate Streamed Chunks</strong></summary>\n *\n * ```typescript\n * import { AIMessageChunk } from '@langchain/core/messages';\n * import { concat } from '@langchain/core/utils/stream';\n *\n * const stream = await llm.stream(input);\n * let full: AIMessageChunk | undefined;\n * for await (const chunk of stream) {\n * full = !full ? chunk : concat(full, chunk);\n * }\n * console.log(full);\n * ```\n *\n * ```txt\n * AIMessageChunk {\n * \"content\": \"\\\"J'adore programmer\\\" \\n\",\n * \"additional_kwargs\": {},\n * \"response_metadata\": {\n * \"finishReason\": \"stop\"\n * },\n * \"tool_calls\": [],\n * \"tool_call_chunks\": [],\n * \"invalid_tool_calls\": [],\n * \"usage_metadata\": {\n * \"input_tokens\": 9,\n * \"output_tokens\": 8,\n * \"total_tokens\": 17\n * }\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Bind tools</strong></summary>\n *\n * ```typescript\n * import { z } from 'zod';\n *\n * const GetWeather = {\n * name: \"GetWeather\",\n * description: \"Get the current weather in a given location\",\n * schema: z.object({\n * location: z.string().describe(\"The city and state, e.g. San Francisco, CA\")\n * }),\n * }\n *\n * const GetPopulation = {\n * name: \"GetPopulation\",\n * description: \"Get the current population in a given location\",\n * schema: z.object({\n * location: z.string().describe(\"The city and state, e.g. San Francisco, CA\")\n * }),\n * }\n *\n * const llmWithTools = llm.bindTools([GetWeather, GetPopulation]);\n * const aiMsg = await llmWithTools.invoke(\n * \"Which city is hotter today and which is bigger: LA or NY?\"\n * );\n * console.log(aiMsg.tool_calls);\n * ```\n *\n * ```txt\n * [\n * {\n * name: 'GetPopulation',\n * args: { location: 'New York City, NY' },\n * id: '33c1c1f47e2f492799c77d2800a43912',\n * type: 'tool_call'\n * }\n * ]\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Structured Output</strong></summary>\n *\n * ```typescript\n * import { z } from 'zod';\n *\n * const Joke = z.object({\n * setup: z.string().describe(\"The setup of the joke\"),\n * punchline: z.string().describe(\"The punchline to the joke\"),\n * rating: z.number().optional().describe(\"How funny the joke is, from 1 to 10\")\n * }).describe('Joke to tell user.');\n *\n * const structuredLlm = llm.withStructuredOutput(Joke, { name: \"Joke\" });\n * const jokeResult = await structuredLlm.invoke(\"Tell me a joke about cats\");\n * console.log(jokeResult);\n * ```\n *\n * ```txt\n * {\n * setup: 'What do you call a cat that loves to bowl?',\n * punchline: 'An alley cat!'\n * }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Usage Metadata</strong></summary>\n *\n * ```typescript\n * const aiMsgForMetadata = await llm.invoke(input);\n * console.log(aiMsgForMetadata.usage_metadata);\n * ```\n *\n * ```txt\n * { input_tokens: 9, output_tokens: 8, total_tokens: 17 }\n * ```\n * </details>\n *\n * <br />\n *\n * <details>\n * <summary><strong>Stream Usage Metadata</strong></summary>\n *\n * ```typescript\n * const streamForMetadata = await llm.stream(\n * input,\n * {\n * streamUsage: true\n * }\n * );\n * let fullForMetadata: AIMessageChunk | undefined;\n * for await (const chunk of streamForMetadata) {\n * fullForMetadata = !fullForMetadata ? chunk : concat(fullForMetadata, chunk);\n * }\n * console.log(fullForMetadata?.usage_metadata);\n * ```\n *\n * ```txt\n * { input_tokens: 9, output_tokens: 8, total_tokens: 17 }\n * ```\n * </details>\n *\n * <br />\n */\nexport class ChatVertexAI extends ChatGoogle {\n lc_namespace = ['langchain', 'chat_models', 'vertexai'];\n dynamicThinkingBudget = false;\n thinkingConfig?: GoogleThinkingConfig;\n\n static lc_name(): 'LibreChatVertexAI' {\n return 'LibreChatVertexAI';\n }\n\n constructor(model: string, fields?: Omit<VertexAIClientOptions, 'model'>);\n constructor(fields?: VertexAIClientOptions);\n constructor(\n modelOrFields?: string | VertexAIClientOptions,\n params?: Omit<VertexAIClientOptions, 'model'>\n ) {\n const fields =\n typeof modelOrFields === 'string'\n ? { ...(params ?? {}), model: modelOrFields }\n : modelOrFields;\n const dynamicThinkingBudget = fields?.thinkingBudget === -1;\n super({\n ...fields,\n platformType: 'gcp',\n });\n this.dynamicThinkingBudget = dynamicThinkingBudget;\n this.thinkingConfig = fields?.thinkingConfig;\n }\n invocationParams(\n options?: this['ParsedCallOptions'] | undefined\n ): GoogleAIModelRequestParams {\n const params = super.invocationParams(options);\n if (this.dynamicThinkingBudget) {\n params.maxReasoningTokens = -1;\n }\n return params;\n }\n async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'],\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n let lastGoodUsage: UsageMetadata | undefined;\n for await (const chunk of super._streamResponseChunks(\n messages,\n options,\n runManager\n )) {\n const genUsage = (\n chunk.generationInfo as { usage_metadata?: UsageMetadata } | undefined\n )?.usage_metadata;\n if (genUsage) {\n lastGoodUsage = genUsage;\n }\n if (chunk.message instanceof AIMessageChunk) {\n const repaired = repairStreamUsageMetadata(\n chunk.message.usage_metadata,\n lastGoodUsage\n );\n if (repaired !== chunk.message.usage_metadata) {\n chunk.message.usage_metadata = repaired;\n }\n }\n yield chunk;\n }\n }\n buildConnection(\n fields: VertexAIClientOptions | undefined,\n client: GoogleAbstractedClient\n ): void {\n // Note: buildConnection is called from super() BEFORE this.thinkingConfig is set,\n // so we must read thinkingConfig from `fields` directly.\n const thinkingConfig = fields?.thinkingConfig ?? this.thinkingConfig;\n\n const connection = new CustomChatConnection(\n { ...fields, ...this },\n this.caller,\n client,\n false\n );\n connection.thinkingConfig = thinkingConfig;\n this.connection = connection;\n\n const streamedConnection = new CustomChatConnection(\n { ...fields, ...this },\n this.caller,\n client,\n true\n );\n streamedConnection.thinkingConfig = thinkingConfig;\n this.streamedConnection = streamedConnection;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -32,6 +32,9 @@ import './local/attachments.mjs';
32
32
  function isSend(value) {
33
33
  return value instanceof Send;
34
34
  }
35
+ function isHandoffToolName(name) {
36
+ return name.startsWith(Constants.LC_TRANSFER_TO_);
37
+ }
35
38
  /**
36
39
  * Format a fail-closed diagnostic for malformed approval-decision
37
40
  * fields. Hosts deserialize resume payloads from untyped JSON, so
@@ -394,6 +397,58 @@ class ToolNode extends RunnableCallable {
394
397
  getFileCheckpointer() {
395
398
  return this.fileCheckpointer;
396
399
  }
400
+ *getRegisteredHandoffNames() {
401
+ if (this.directToolNames != null) {
402
+ for (const toolName of this.directToolNames) {
403
+ yield toolName;
404
+ }
405
+ }
406
+ for (const toolName of this.toolMap.keys()) {
407
+ if (this.directToolNames?.has(toolName) === true) {
408
+ continue;
409
+ }
410
+ yield toolName;
411
+ }
412
+ }
413
+ hasRegisteredHandoffTool() {
414
+ for (const toolName of this.getRegisteredHandoffNames()) {
415
+ if (isHandoffToolName(toolName)) {
416
+ return true;
417
+ }
418
+ }
419
+ return false;
420
+ }
421
+ getHandoffToolNameSuggestion(callName) {
422
+ if (!isHandoffToolName(callName)) {
423
+ return undefined;
424
+ }
425
+ let suggestion;
426
+ for (const toolName of this.getRegisteredHandoffNames()) {
427
+ if (!isHandoffToolName(toolName) ||
428
+ toolName.length >= callName.length ||
429
+ !callName.startsWith(toolName)) {
430
+ continue;
431
+ }
432
+ if (suggestion == null || toolName.length > suggestion.length) {
433
+ suggestion = toolName;
434
+ }
435
+ }
436
+ return suggestion;
437
+ }
438
+ shouldHandleUnknownHandoffLocally(callName, hasRegisteredHandoffTool) {
439
+ if (!isHandoffToolName(callName) || this.toolMap.has(callName)) {
440
+ return false;
441
+ }
442
+ return hasRegisteredHandoffTool ?? this.hasRegisteredHandoffTool();
443
+ }
444
+ getUnknownToolErrorMessage(callName) {
445
+ const suggestion = this.getHandoffToolNameSuggestion(callName);
446
+ if (suggestion == null) {
447
+ return `Tool "${callName}" not found.`;
448
+ }
449
+ return (`Tool "${callName}" not found. Did you mean "${suggestion}"? ` +
450
+ 'Handoff tool names must match exactly.');
451
+ }
397
452
  /**
398
453
  * Flush the per-Run direct-path turn cache. Called by the Graph at
399
454
  * end-of-Run via `clearHeavyState`. The map intentionally survives
@@ -479,7 +534,7 @@ class ToolNode extends RunnableCallable {
479
534
  const runId = batchScopeId ?? config.configurable?.run_id;
480
535
  try {
481
536
  if (tool === undefined) {
482
- throw new Error(`Tool "${call.name}" not found.`);
537
+ throw new Error(this.getUnknownToolErrorMessage(call.name));
483
538
  }
484
539
  /**
485
540
  * `usageCount` is the per-tool-name invocation index that
@@ -2121,8 +2176,9 @@ class ToolNode extends RunnableCallable {
2121
2176
  const turn = this.toolOutputRegistry?.nextTurn(batchScopeId) ?? 0;
2122
2177
  let outputs;
2123
2178
  if (this.isSendInput(input)) {
2124
- const isDirectTool = this.directToolNames?.has(input.lg_tool_call.name);
2125
- if (this.eventDrivenMode && isDirectTool !== true) {
2179
+ const isLocalTool = this.directToolNames?.has(input.lg_tool_call.name) === true ||
2180
+ this.shouldHandleUnknownHandoffLocally(input.lg_tool_call.name);
2181
+ if (this.eventDrivenMode && !isLocalTool) {
2126
2182
  return this.executeViaEvent([input.lg_tool_call], config, input, {
2127
2183
  batchIndices: [0],
2128
2184
  turn,
@@ -2205,26 +2261,28 @@ class ToolNode extends RunnableCallable {
2205
2261
  false));
2206
2262
  }) ?? [];
2207
2263
  if (this.eventDrivenMode && filteredCalls.length > 0) {
2208
- const filteredIndices = filteredCalls.map((_, idx) => idx);
2209
- if (!this.directToolNames || this.directToolNames.size === 0) {
2210
- return this.executeViaEvent(filteredCalls, config, input, {
2211
- batchIndices: filteredIndices,
2212
- turn,
2213
- batchScopeId,
2214
- });
2215
- }
2264
+ const directToolNames = this.directToolNames;
2265
+ const hasRegisteredHandoffTool = this.hasRegisteredHandoffTool();
2216
2266
  const directEntries = [];
2217
2267
  const eventEntries = [];
2218
2268
  for (let i = 0; i < filteredCalls.length; i++) {
2219
2269
  const call = filteredCalls[i];
2220
2270
  const entry = { call, batchIndex: i };
2221
- if (this.directToolNames.has(call.name)) {
2271
+ if (directToolNames?.has(call.name) === true ||
2272
+ this.shouldHandleUnknownHandoffLocally(call.name, hasRegisteredHandoffTool)) {
2222
2273
  directEntries.push(entry);
2223
2274
  }
2224
2275
  else {
2225
2276
  eventEntries.push(entry);
2226
2277
  }
2227
2278
  }
2279
+ if (directEntries.length === 0) {
2280
+ return this.executeViaEvent(filteredCalls, config, input, {
2281
+ batchIndices: eventEntries.map((entry) => entry.batchIndex),
2282
+ turn,
2283
+ batchScopeId,
2284
+ });
2285
+ }
2228
2286
  const directCalls = directEntries.map((e) => e.call);
2229
2287
  const directIndices = directEntries.map((e) => e.batchIndex);
2230
2288
  const eventCalls = eventEntries.map((e) => e.call);