@librechat/agents 3.1.81 → 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 (47) 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/tools/ToolNode.cjs +70 -12
  12. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  13. package/dist/esm/agents/AgentContext.mjs +101 -34
  14. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  15. package/dist/esm/graphs/Graph.mjs +13 -0
  16. package/dist/esm/graphs/Graph.mjs.map +1 -1
  17. package/dist/esm/llm/openai/index.mjs +50 -14
  18. package/dist/esm/llm/openai/index.mjs.map +1 -1
  19. package/dist/esm/llm/openrouter/index.mjs +17 -7
  20. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  21. package/dist/esm/llm/openrouter/toolCache.mjs +53 -0
  22. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -0
  23. package/dist/esm/tools/ToolNode.mjs +70 -12
  24. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  25. package/dist/types/agents/AgentContext.d.ts +6 -1
  26. package/dist/types/llm/openrouter/index.d.ts +1 -0
  27. package/dist/types/llm/openrouter/toolCache.d.ts +2 -0
  28. package/dist/types/tools/ToolNode.d.ts +5 -0
  29. package/dist/types/types/run.d.ts +2 -0
  30. package/package.json +2 -1
  31. package/src/agents/AgentContext.ts +146 -38
  32. package/src/agents/__tests__/AgentContext.test.ts +198 -0
  33. package/src/graphs/Graph.ts +24 -0
  34. package/src/llm/custom-chat-models.smoke.test.ts +76 -0
  35. package/src/llm/openai/deepseek.test.ts +14 -1
  36. package/src/llm/openai/index.ts +38 -12
  37. package/src/llm/openrouter/index.ts +22 -7
  38. package/src/llm/openrouter/reasoning.test.ts +33 -0
  39. package/src/llm/openrouter/toolCache.test.ts +83 -0
  40. package/src/llm/openrouter/toolCache.ts +89 -0
  41. package/src/messages/cache.test.ts +127 -0
  42. package/src/scripts/openrouter_prompt_cache_live.ts +310 -0
  43. package/src/specs/agent-handoffs.live.test.ts +140 -0
  44. package/src/specs/agent-handoffs.test.ts +266 -2
  45. package/src/specs/openrouter.simple.test.ts +15 -8
  46. package/src/tools/ToolNode.ts +92 -13
  47. package/src/types/run.ts +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","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":["ChatOpenAI"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","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":["ChatOpenAI"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ var index = require('../openai/index.cjs');
4
+
5
+ const CACHE_CONTROL = { type: 'ephemeral' };
6
+ function getToolName(tool) {
7
+ const candidate = tool;
8
+ if (typeof candidate.name === 'string') {
9
+ return candidate.name;
10
+ }
11
+ if (typeof candidate.function?.name === 'string') {
12
+ return candidate.function.name;
13
+ }
14
+ return undefined;
15
+ }
16
+ function hasDeferredMarker(tool) {
17
+ return tool.defer_loading === true;
18
+ }
19
+ function toOpenRouterTool(tool) {
20
+ const converted = index._convertToOpenAITool(tool);
21
+ if (hasDeferredMarker(tool)) {
22
+ return { ...converted, defer_loading: true };
23
+ }
24
+ return converted;
25
+ }
26
+ function markCacheControl(tool) {
27
+ return {
28
+ ...tool,
29
+ cache_control: CACHE_CONTROL,
30
+ };
31
+ }
32
+ function partitionAndMarkOpenRouterToolCache(tools, isDeferred) {
33
+ if (tools == null || tools.length === 0) {
34
+ return tools;
35
+ }
36
+ const staticTools = [];
37
+ const deferredTools = [];
38
+ for (const tool of tools) {
39
+ const converted = toOpenRouterTool(tool);
40
+ const name = getToolName(converted) ?? getToolName(tool);
41
+ if (name != null && isDeferred(name)) {
42
+ deferredTools.push(converted);
43
+ continue;
44
+ }
45
+ staticTools.push(converted);
46
+ }
47
+ if (staticTools.length === 0) {
48
+ return [...deferredTools];
49
+ }
50
+ staticTools[staticTools.length - 1] = markCacheControl(staticTools[staticTools.length - 1]);
51
+ return [...staticTools, ...deferredTools];
52
+ }
53
+
54
+ exports.partitionAndMarkOpenRouterToolCache = partitionAndMarkOpenRouterToolCache;
55
+ //# sourceMappingURL=toolCache.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCache.cjs","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":["_convertToOpenAITool"],"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,GAAGA,0BAAoB,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;;;;"}
@@ -34,6 +34,9 @@ require('./local/attachments.cjs');
34
34
  function isSend(value) {
35
35
  return value instanceof langgraph.Send;
36
36
  }
37
+ function isHandoffToolName(name) {
38
+ return name.startsWith(_enum.Constants.LC_TRANSFER_TO_);
39
+ }
37
40
  /**
38
41
  * Format a fail-closed diagnostic for malformed approval-decision
39
42
  * fields. Hosts deserialize resume payloads from untyped JSON, so
@@ -396,6 +399,58 @@ class ToolNode extends run.RunnableCallable {
396
399
  getFileCheckpointer() {
397
400
  return this.fileCheckpointer;
398
401
  }
402
+ *getRegisteredHandoffNames() {
403
+ if (this.directToolNames != null) {
404
+ for (const toolName of this.directToolNames) {
405
+ yield toolName;
406
+ }
407
+ }
408
+ for (const toolName of this.toolMap.keys()) {
409
+ if (this.directToolNames?.has(toolName) === true) {
410
+ continue;
411
+ }
412
+ yield toolName;
413
+ }
414
+ }
415
+ hasRegisteredHandoffTool() {
416
+ for (const toolName of this.getRegisteredHandoffNames()) {
417
+ if (isHandoffToolName(toolName)) {
418
+ return true;
419
+ }
420
+ }
421
+ return false;
422
+ }
423
+ getHandoffToolNameSuggestion(callName) {
424
+ if (!isHandoffToolName(callName)) {
425
+ return undefined;
426
+ }
427
+ let suggestion;
428
+ for (const toolName of this.getRegisteredHandoffNames()) {
429
+ if (!isHandoffToolName(toolName) ||
430
+ toolName.length >= callName.length ||
431
+ !callName.startsWith(toolName)) {
432
+ continue;
433
+ }
434
+ if (suggestion == null || toolName.length > suggestion.length) {
435
+ suggestion = toolName;
436
+ }
437
+ }
438
+ return suggestion;
439
+ }
440
+ shouldHandleUnknownHandoffLocally(callName, hasRegisteredHandoffTool) {
441
+ if (!isHandoffToolName(callName) || this.toolMap.has(callName)) {
442
+ return false;
443
+ }
444
+ return hasRegisteredHandoffTool ?? this.hasRegisteredHandoffTool();
445
+ }
446
+ getUnknownToolErrorMessage(callName) {
447
+ const suggestion = this.getHandoffToolNameSuggestion(callName);
448
+ if (suggestion == null) {
449
+ return `Tool "${callName}" not found.`;
450
+ }
451
+ return (`Tool "${callName}" not found. Did you mean "${suggestion}"? ` +
452
+ 'Handoff tool names must match exactly.');
453
+ }
399
454
  /**
400
455
  * Flush the per-Run direct-path turn cache. Called by the Graph at
401
456
  * end-of-Run via `clearHeavyState`. The map intentionally survives
@@ -481,7 +536,7 @@ class ToolNode extends run.RunnableCallable {
481
536
  const runId = batchScopeId ?? config.configurable?.run_id;
482
537
  try {
483
538
  if (tool === undefined) {
484
- throw new Error(`Tool "${call.name}" not found.`);
539
+ throw new Error(this.getUnknownToolErrorMessage(call.name));
485
540
  }
486
541
  /**
487
542
  * `usageCount` is the per-tool-name invocation index that
@@ -2123,8 +2178,9 @@ class ToolNode extends run.RunnableCallable {
2123
2178
  const turn = this.toolOutputRegistry?.nextTurn(batchScopeId) ?? 0;
2124
2179
  let outputs;
2125
2180
  if (this.isSendInput(input)) {
2126
- const isDirectTool = this.directToolNames?.has(input.lg_tool_call.name);
2127
- if (this.eventDrivenMode && isDirectTool !== true) {
2181
+ const isLocalTool = this.directToolNames?.has(input.lg_tool_call.name) === true ||
2182
+ this.shouldHandleUnknownHandoffLocally(input.lg_tool_call.name);
2183
+ if (this.eventDrivenMode && !isLocalTool) {
2128
2184
  return this.executeViaEvent([input.lg_tool_call], config, input, {
2129
2185
  batchIndices: [0],
2130
2186
  turn,
@@ -2207,26 +2263,28 @@ class ToolNode extends run.RunnableCallable {
2207
2263
  false));
2208
2264
  }) ?? [];
2209
2265
  if (this.eventDrivenMode && filteredCalls.length > 0) {
2210
- const filteredIndices = filteredCalls.map((_, idx) => idx);
2211
- if (!this.directToolNames || this.directToolNames.size === 0) {
2212
- return this.executeViaEvent(filteredCalls, config, input, {
2213
- batchIndices: filteredIndices,
2214
- turn,
2215
- batchScopeId,
2216
- });
2217
- }
2266
+ const directToolNames = this.directToolNames;
2267
+ const hasRegisteredHandoffTool = this.hasRegisteredHandoffTool();
2218
2268
  const directEntries = [];
2219
2269
  const eventEntries = [];
2220
2270
  for (let i = 0; i < filteredCalls.length; i++) {
2221
2271
  const call = filteredCalls[i];
2222
2272
  const entry = { call, batchIndex: i };
2223
- if (this.directToolNames.has(call.name)) {
2273
+ if (directToolNames?.has(call.name) === true ||
2274
+ this.shouldHandleUnknownHandoffLocally(call.name, hasRegisteredHandoffTool)) {
2224
2275
  directEntries.push(entry);
2225
2276
  }
2226
2277
  else {
2227
2278
  eventEntries.push(entry);
2228
2279
  }
2229
2280
  }
2281
+ if (directEntries.length === 0) {
2282
+ return this.executeViaEvent(filteredCalls, config, input, {
2283
+ batchIndices: eventEntries.map((entry) => entry.batchIndex),
2284
+ turn,
2285
+ batchScopeId,
2286
+ });
2287
+ }
2230
2288
  const directCalls = directEntries.map((e) => e.call);
2231
2289
  const directIndices = directEntries.map((e) => e.batchIndex);
2232
2290
  const eventCalls = eventEntries.map((e) => e.call);