@librechat/agents 3.2.34 → 3.2.35

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 (77) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +47 -10
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +13 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +121 -3
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/llm/invoke.cjs +49 -8
  8. package/dist/cjs/llm/invoke.cjs.map +1 -1
  9. package/dist/cjs/main.cjs +2 -0
  10. package/dist/cjs/messages/content.cjs +12 -14
  11. package/dist/cjs/messages/content.cjs.map +1 -1
  12. package/dist/cjs/messages/prune.cjs +31 -13
  13. package/dist/cjs/messages/prune.cjs.map +1 -1
  14. package/dist/cjs/run.cjs +7 -2
  15. package/dist/cjs/run.cjs.map +1 -1
  16. package/dist/cjs/summarization/node.cjs +12 -1
  17. package/dist/cjs/summarization/node.cjs.map +1 -1
  18. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +138 -2
  19. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  20. package/dist/cjs/utils/tokens.cjs +30 -0
  21. package/dist/cjs/utils/tokens.cjs.map +1 -1
  22. package/dist/esm/agents/AgentContext.mjs +47 -10
  23. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  24. package/dist/esm/common/enum.mjs +13 -0
  25. package/dist/esm/common/enum.mjs.map +1 -1
  26. package/dist/esm/graphs/Graph.mjs +122 -4
  27. package/dist/esm/graphs/Graph.mjs.map +1 -1
  28. package/dist/esm/llm/invoke.mjs +49 -8
  29. package/dist/esm/llm/invoke.mjs.map +1 -1
  30. package/dist/esm/main.mjs +3 -3
  31. package/dist/esm/messages/content.mjs +12 -15
  32. package/dist/esm/messages/content.mjs.map +1 -1
  33. package/dist/esm/messages/prune.mjs +31 -13
  34. package/dist/esm/messages/prune.mjs.map +1 -1
  35. package/dist/esm/run.mjs +7 -2
  36. package/dist/esm/run.mjs.map +1 -1
  37. package/dist/esm/summarization/node.mjs +12 -1
  38. package/dist/esm/summarization/node.mjs.map +1 -1
  39. package/dist/esm/tools/subagent/SubagentExecutor.mjs +138 -2
  40. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  41. package/dist/esm/utils/tokens.mjs +30 -1
  42. package/dist/esm/utils/tokens.mjs.map +1 -1
  43. package/dist/types/agents/AgentContext.d.ts +7 -3
  44. package/dist/types/common/enum.d.ts +13 -0
  45. package/dist/types/graphs/Graph.d.ts +8 -1
  46. package/dist/types/llm/invoke.d.ts +1 -1
  47. package/dist/types/messages/content.d.ts +5 -0
  48. package/dist/types/messages/prune.d.ts +4 -0
  49. package/dist/types/run.d.ts +1 -0
  50. package/dist/types/tools/subagent/SubagentExecutor.d.ts +11 -1
  51. package/dist/types/types/graph.d.ts +89 -3
  52. package/dist/types/types/run.d.ts +13 -0
  53. package/dist/types/utils/tokens.d.ts +7 -0
  54. package/package.json +1 -1
  55. package/src/agents/AgentContext.ts +69 -6
  56. package/src/agents/__tests__/AgentContext.test.ts +6 -2
  57. package/src/common/enum.ts +13 -0
  58. package/src/graphs/Graph.ts +196 -0
  59. package/src/llm/invoke.test.ts +79 -1
  60. package/src/llm/invoke.ts +58 -4
  61. package/src/messages/content.ts +24 -32
  62. package/src/messages/prune.ts +39 -2
  63. package/src/run.ts +5 -0
  64. package/src/scripts/subagent-usage-sink.ts +176 -0
  65. package/src/specs/context-accuracy.live.test.ts +409 -0
  66. package/src/specs/context-usage-event.test.ts +117 -0
  67. package/src/specs/context-usage.live.test.ts +297 -0
  68. package/src/specs/prune.test.ts +51 -1
  69. package/src/specs/subagent.test.ts +124 -1
  70. package/src/summarization/__tests__/node.test.ts +60 -1
  71. package/src/summarization/node.ts +20 -1
  72. package/src/tools/__tests__/SubagentExecutor.test.ts +443 -1
  73. package/src/tools/subagent/SubagentExecutor.ts +221 -3
  74. package/src/types/graph.ts +94 -1
  75. package/src/types/run.ts +13 -0
  76. package/src/utils/__tests__/apportion.test.ts +32 -0
  77. package/src/utils/tokens.ts +33 -0
@@ -75,6 +75,22 @@ async function attemptInvoke({ model, messages, provider, context, onChunk }, co
75
75
  const registry = context?.getOrCreateToolOutputRegistry();
76
76
  const runId = config?.configurable?.run_id;
77
77
  const messagesForProvider = require_toolOutputReferences.annotateMessagesForLLM(messages, registry, runId);
78
+ /**
79
+ * Stamp the provider that is ACTUALLY serving this invocation onto the
80
+ * callback metadata. `attemptInvoke` is the single funnel for primary,
81
+ * fallback, and summarization model calls, so consumers that need
82
+ * provider attribution per call (the subagent usage-capture handler)
83
+ * read this key instead of trusting static agent config — which is
84
+ * wrong for fallback-served calls — or `ls_provider` — which derived
85
+ * providers inherit from their base class.
86
+ */
87
+ config = {
88
+ ...config,
89
+ metadata: {
90
+ ...config?.metadata ?? {},
91
+ ["__invoked_provider"]: provider
92
+ }
93
+ };
78
94
  if (model.stream) {
79
95
  const stream = await model.stream(messagesForProvider, config);
80
96
  let finalChunk;
@@ -88,7 +104,7 @@ async function attemptInvoke({ model, messages, provider, context, onChunk }, co
88
104
  });
89
105
  }
90
106
  else if (registeredStreamHandler == null) {
91
- const metadata = config?.metadata;
107
+ const metadata = config.metadata;
92
108
  const streamHandler = new require_stream.ChatModelStreamHandler();
93
109
  for await (const chunk of stream) {
94
110
  const handlingChunk = getStreamHandlingChunk({
@@ -104,7 +120,7 @@ async function attemptInvoke({ model, messages, provider, context, onChunk }, co
104
120
  });
105
121
  }
106
122
  } else {
107
- const metadata = config?.metadata;
123
+ const metadata = config.metadata;
108
124
  for await (const chunk of stream) {
109
125
  const handlingChunk = getStreamHandlingChunk({
110
126
  current: finalChunk,
@@ -128,23 +144,48 @@ async function attemptInvoke({ model, messages, provider, context, onChunk }, co
128
144
  return { messages: [finalMessage] };
129
145
  }
130
146
  /**
147
+ * Best-effort read of the configured model name from client options.
148
+ * Providers disagree on the key (`model` vs `modelName`).
149
+ */
150
+ function extractClientOptionsModel(clientOptions) {
151
+ const options = clientOptions;
152
+ if (typeof options?.model === "string" && options.model !== "") return options.model;
153
+ if (typeof options?.modelName === "string" && options.modelName !== "") return options.modelName;
154
+ }
155
+ /**
131
156
  * Attempts each fallback provider in order until one succeeds.
132
157
  * Throws the last error if all fallbacks fail.
133
158
  */
134
159
  async function tryFallbackProviders({ fallbacks, tools, messages, config, primaryError, context, onChunk }) {
135
160
  let lastError = primaryError;
136
161
  for (const fb of fallbacks) try {
162
+ const fbModel = require_init.initializeModel({
163
+ provider: fb.provider,
164
+ clientOptions: fb.clientOptions,
165
+ tools
166
+ });
167
+ /**
168
+ * Stamp the fallback's configured model onto callback metadata so
169
+ * per-call attribution (subagent usage capture) doesn't fall back to
170
+ * the PRIMARY config's model when the provider reports no
171
+ * `ls_model_name`. The serving provider is stamped uniformly by
172
+ * `attemptInvoke` (`INVOKED_PROVIDER`).
173
+ */
174
+ const fbModelName = extractClientOptionsModel(fb.clientOptions);
175
+ const fbConfig = fbModelName == null ? config : {
176
+ ...config,
177
+ metadata: {
178
+ ...config?.metadata ?? {},
179
+ ["__invoked_model"]: fbModelName
180
+ }
181
+ };
137
182
  return await attemptInvoke({
138
- model: require_init.initializeModel({
139
- provider: fb.provider,
140
- clientOptions: fb.clientOptions,
141
- tools
142
- }),
183
+ model: fbModel,
143
184
  messages,
144
185
  provider: fb.provider,
145
186
  context,
146
187
  onChunk
147
- }, config);
188
+ }, fbConfig);
148
189
  } catch (e) {
149
190
  lastError = e;
150
191
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"invoke.cjs","names":["ChatModelStreamHandler","AIMessageChunk","annotateMessagesForLLM","manualToolStreamProviders","modifyDeltaProperties","initializeModel"],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { GraphEvents, Providers } from '@/common';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config?.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config?.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n return { messages: [finalMessage] };\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n * Throws the last error if all fallbacks fail.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n}: {\n fallbacks: Array<{ provider: Providers; clientOptions?: t.ClientOptions }>;\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n let lastError: unknown = primaryError;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n config\n );\n return result;\n } catch (e) {\n lastError = e;\n continue;\n }\n }\n if (lastError !== undefined) {\n throw lastError;\n }\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"invoke.cjs","names":["ChatModelStreamHandler","AIMessageChunk","annotateMessagesForLLM","manualToolStreamProviders","modifyDeltaProperties","initializeModel"],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { Constants, GraphEvents, Providers } from '@/common';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n /**\n * Stamp the provider that is ACTUALLY serving this invocation onto the\n * callback metadata. `attemptInvoke` is the single funnel for primary,\n * fallback, and summarization model calls, so consumers that need\n * provider attribution per call (the subagent usage-capture handler)\n * read this key instead of trusting static agent config — which is\n * wrong for fallback-served calls — or `ls_provider` — which derived\n * providers inherit from their base class.\n */\n config = {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_PROVIDER]: provider,\n },\n };\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n return { messages: [finalMessage] };\n}\n\n/**\n * Best-effort read of the configured model name from client options.\n * Providers disagree on the key (`model` vs `modelName`).\n */\nfunction extractClientOptionsModel(\n clientOptions: t.ClientOptions | undefined\n): string | undefined {\n const options = clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof options?.model === 'string' && options.model !== '') {\n return options.model;\n }\n if (typeof options?.modelName === 'string' && options.modelName !== '') {\n return options.modelName;\n }\n return undefined;\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n * Throws the last error if all fallbacks fail.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n}: {\n fallbacks: Array<{ provider: Providers; clientOptions?: t.ClientOptions }>;\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n let lastError: unknown = primaryError;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n /**\n * Stamp the fallback's configured model onto callback metadata so\n * per-call attribution (subagent usage capture) doesn't fall back to\n * the PRIMARY config's model when the provider reports no\n * `ls_model_name`. The serving provider is stamped uniformly by\n * `attemptInvoke` (`INVOKED_PROVIDER`).\n */\n const fbModelName = extractClientOptionsModel(fb.clientOptions);\n const fbConfig: RunnableConfig | undefined =\n fbModelName == null\n ? config\n : {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_MODEL]: fbModelName,\n },\n };\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n fbConfig\n );\n return result;\n } catch (e) {\n lastError = e;\n continue;\n }\n }\n if (lastError !== undefined) {\n throw lastError;\n }\n return undefined;\n}\n"],"mappings":"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"}
package/dist/cjs/main.cjs CHANGED
@@ -339,6 +339,7 @@ exports.appendTmpScratchReminder = require_CodeExecutor.appendTmpScratchReminder
339
339
  exports.applyContextPruning = require_contextPruning.applyContextPruning;
340
340
  exports.applyEdit = require_editStrategies.applyEdit;
341
341
  exports.applyPreToolUseHooksForBridge = require_LocalProgrammaticToolCalling.applyPreToolUseHooksForBridge;
342
+ exports.apportionTokenCounts = require_tokens.apportionTokenCounts;
342
343
  exports.askUserQuestion = require_askUserQuestion.askUserQuestion;
343
344
  exports.attemptInvoke = require_invoke.attemptInvoke;
344
345
  exports.bashAstFindingsToErrors = require_bashAst.bashAstFindingsToErrors;
@@ -514,6 +515,7 @@ Object.defineProperty(exports, "isInterrupted", {
514
515
  return _langchain_langgraph.isInterrupted;
515
516
  }
516
517
  });
518
+ exports.isLegacyConvertible = require_content.isLegacyConvertible;
517
519
  exports.isLikelyContextOverflowError = require_errors.isLikelyContextOverflowError;
518
520
  exports.isOpenAILike = require_llm.isOpenAILike;
519
521
  exports.isPresent = require_misc.isPresent;
@@ -2,6 +2,16 @@ require("../common/enum.cjs");
2
2
  require("../common/index.cjs");
3
3
  //#region src/messages/content.ts
4
4
  /**
5
+ * Whether {@link formatContentStrings} will flatten this message's content:
6
+ * a human/ai/system message whose content is an array of text-only blocks.
7
+ */
8
+ const isLegacyConvertible = (message) => {
9
+ const messageType = message.getType();
10
+ if (!(messageType === "human" || messageType === "ai" || messageType === "system")) return false;
11
+ if (!Array.isArray(message.content)) return false;
12
+ return message.content.every((block) => block.type === "text");
13
+ };
14
+ /**
5
15
  * Formats an array of messages for LangChain, making sure all content fields are strings
6
16
  * @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.
7
17
  * @returns {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} - The array of formatted LangChain messages, including ToolMessages for tool calls.
@@ -9,20 +19,7 @@ require("../common/index.cjs");
9
19
  const formatContentStrings = (payload) => {
10
20
  const result = [];
11
21
  for (const message of payload) {
12
- const messageType = message.getType();
13
- if (!(messageType === "human" || messageType === "ai" || messageType === "system")) {
14
- result.push(message);
15
- continue;
16
- }
17
- if (typeof message.content === "string") {
18
- result.push(message);
19
- continue;
20
- }
21
- if (!Array.isArray(message.content)) {
22
- result.push(message);
23
- continue;
24
- }
25
- if (!message.content.every((block) => block.type === "text")) {
22
+ if (!isLegacyConvertible(message)) {
26
23
  result.push(message);
27
24
  continue;
28
25
  }
@@ -36,5 +33,6 @@ const formatContentStrings = (payload) => {
36
33
  };
37
34
  //#endregion
38
35
  exports.formatContentStrings = formatContentStrings;
36
+ exports.isLegacyConvertible = isLegacyConvertible;
39
37
 
40
38
  //# sourceMappingURL=content.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.cjs","names":[],"sources":["../../../src/messages/content.ts"],"sourcesContent":["import type { BaseMessage } from '@langchain/core/messages';\nimport { ContentTypes } from '@/common';\n\n/**\n * Formats an array of messages for LangChain, making sure all content fields are strings\n * @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.\n * @returns {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} - The array of formatted LangChain messages, including ToolMessages for tool calls.\n */\nexport const formatContentStrings = (\n payload: Array<BaseMessage>\n): Array<BaseMessage> => {\n // Create a new array to store the processed messages\n const result: Array<BaseMessage> = [];\n\n for (const message of payload) {\n const messageType = message.getType();\n const isValidMessage =\n messageType === 'human' ||\n messageType === 'ai' ||\n messageType === 'system';\n\n if (!isValidMessage) {\n result.push(message);\n continue;\n }\n\n // If content is already a string, add as-is\n if (typeof message.content === 'string') {\n result.push(message);\n continue;\n }\n\n // If content is not an array, add as-is\n if (!Array.isArray(message.content)) {\n result.push(message);\n continue;\n }\n\n // Check if all content blocks are text type\n const allTextBlocks = message.content.every(\n (block) => block.type === ContentTypes.TEXT\n );\n\n // Only convert to string if all blocks are text type\n if (!allTextBlocks) {\n result.push(message);\n continue;\n }\n\n // Reduce text types to a single string\n const content = message.content.reduce((acc, curr) => {\n if (curr.type === ContentTypes.TEXT) {\n return `${acc}${curr[ContentTypes.TEXT] || ''}\\n`;\n }\n return acc;\n }, '');\n\n message.content = content.trim();\n result.push(message);\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;AAQA,MAAa,wBACX,YACuB;CAEvB,MAAM,SAA6B,CAAC;CAEpC,KAAK,MAAM,WAAW,SAAS;EAC7B,MAAM,cAAc,QAAQ,QAAQ;EAMpC,IAAI,EAJF,gBAAgB,WAChB,gBAAgB,QAChB,gBAAgB,WAEG;GACnB,OAAO,KAAK,OAAO;GACnB;EACF;EAGA,IAAI,OAAO,QAAQ,YAAY,UAAU;GACvC,OAAO,KAAK,OAAO;GACnB;EACF;EAGA,IAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,GAAG;GACnC,OAAO,KAAK,OAAO;GACnB;EACF;EAQA,IAAI,CALkB,QAAQ,QAAQ,OACnC,UAAU,MAAM,SAAA,MAIF,GAAG;GAClB,OAAO,KAAK,OAAO;GACnB;EACF;EAUA,QAAQ,UAPQ,QAAQ,QAAQ,QAAQ,KAAK,SAAS;GACpD,IAAI,KAAK,SAAA,QACP,OAAO,GAAG,MAAM,KAAA,WAA2B,GAAG;GAEhD,OAAO;EACT,GAAG,EAEqB,CAAC,CAAC,KAAK;EAC/B,OAAO,KAAK,OAAO;CACrB;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"content.cjs","names":[],"sources":["../../../src/messages/content.ts"],"sourcesContent":["import type {\n BaseMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport { ContentTypes } from '@/common';\n\n/**\n * Whether {@link formatContentStrings} will flatten this message's content:\n * a human/ai/system message whose content is an array of text-only blocks.\n */\nexport const isLegacyConvertible = (message: BaseMessage): boolean => {\n const messageType = message.getType();\n const isValidMessage =\n messageType === 'human' || messageType === 'ai' || messageType === 'system';\n if (!isValidMessage) {\n return false;\n }\n if (!Array.isArray(message.content)) {\n return false;\n }\n return message.content.every((block) => block.type === ContentTypes.TEXT);\n};\n\n/**\n * Formats an array of messages for LangChain, making sure all content fields are strings\n * @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.\n * @returns {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} - The array of formatted LangChain messages, including ToolMessages for tool calls.\n */\nexport const formatContentStrings = (\n payload: Array<BaseMessage>\n): Array<BaseMessage> => {\n // Create a new array to store the processed messages\n const result: Array<BaseMessage> = [];\n\n for (const message of payload) {\n if (!isLegacyConvertible(message)) {\n result.push(message);\n continue;\n }\n\n // Reduce text types to a single string\n const blocks = message.content as MessageContentComplex[];\n const content = blocks.reduce((acc, curr) => {\n if (curr.type === ContentTypes.TEXT) {\n return `${acc}${curr[ContentTypes.TEXT] || ''}\\n`;\n }\n return acc;\n }, '');\n\n message.content = content.trim();\n result.push(message);\n }\n\n return result;\n};\n"],"mappings":";;;;;;;AAUA,MAAa,uBAAuB,YAAkC;CACpE,MAAM,cAAc,QAAQ,QAAQ;CAGpC,IAAI,EADF,gBAAgB,WAAW,gBAAgB,QAAQ,gBAAgB,WAEnE,OAAO;CAET,IAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,GAChC,OAAO;CAET,OAAO,QAAQ,QAAQ,OAAO,UAAU,MAAM,SAAA,MAA0B;AAC1E;;;;;;AAOA,MAAa,wBACX,YACuB;CAEvB,MAAM,SAA6B,CAAC;CAEpC,KAAK,MAAM,WAAW,SAAS;EAC7B,IAAI,CAAC,oBAAoB,OAAO,GAAG;GACjC,OAAO,KAAK,OAAO;GACnB;EACF;EAWA,QAAQ,UARO,QAAQ,QACA,QAAQ,KAAK,SAAS;GAC3C,IAAI,KAAK,SAAA,QACP,OAAO,GAAG,MAAM,KAAA,WAA2B,GAAG;GAEhD,OAAO;EACT,GAAG,EAEqB,CAAC,CAAC,KAAK;EAC/B,OAAO,KAAK,OAAO;CACrB;CAEA,OAAO;AACT"}
@@ -685,15 +685,24 @@ function createPruneMessages(factoryParams) {
685
685
  let originalToolContentSize = 0;
686
686
  const contextPruningSettings = require_contextPruningSettings.resolveContextPruningSettings(factoryParams.contextPruningConfig);
687
687
  return function pruneMessages(params) {
688
- if (params.messages.length === 0) return {
689
- context: [],
690
- indexTokenCountMap,
691
- messagesToRefine: [],
692
- prePruneContextTokens: 0,
693
- remainingContextTokens: factoryParams.maxTokens,
694
- calibrationRatio,
695
- resolvedInstructionOverhead: bestInstructionOverhead
696
- };
688
+ if (params.messages.length === 0) {
689
+ /** Post-compaction calls still invoke the model — report the same
690
+ * reserve-adjusted budget fields as the populated paths */
691
+ const emptyInstructionTokens = factoryParams.getInstructionTokens?.() ?? 0;
692
+ const emptyReserveRatio = factoryParams.reserveRatio ?? .05;
693
+ const emptyBudget = factoryParams.maxTokens - (emptyReserveRatio > 0 && emptyReserveRatio < 1 ? Math.round(factoryParams.maxTokens * emptyReserveRatio) : 0);
694
+ return {
695
+ context: [],
696
+ indexTokenCountMap,
697
+ messagesToRefine: [],
698
+ prePruneContextTokens: 0,
699
+ remainingContextTokens: Math.max(0, emptyBudget - emptyInstructionTokens),
700
+ calibrationRatio,
701
+ resolvedInstructionOverhead: bestInstructionOverhead,
702
+ contextBudget: emptyBudget,
703
+ effectiveInstructionTokens: emptyInstructionTokens
704
+ };
705
+ }
697
706
  if (factoryParams.provider === "openAI" && factoryParams.thinkingEnabled === true) for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
698
707
  const m = params.messages[i];
699
708
  if (m.getType() === "ai" && typeof m.additional_kwargs.reasoning_content === "string" && Array.isArray(m.additional_kwargs.provider_specific_fields?.thinking_blocks) && m.tool_calls && (m.tool_calls?.length ?? 0) > 0) {
@@ -799,7 +808,9 @@ function createPruneMessages(factoryParams) {
799
808
  remainingContextTokens: 0,
800
809
  contextPressure: pruningBudget > 0 ? calibratedTotalTokens / pruningBudget : 0,
801
810
  calibrationRatio,
802
- resolvedInstructionOverhead: bestInstructionOverhead
811
+ resolvedInstructionOverhead: bestInstructionOverhead,
812
+ contextBudget: pruningBudget,
813
+ effectiveInstructionTokens: currentInstructionTokens
803
814
  };
804
815
  }
805
816
  totalTokens = sumTokenCounts(indexTokenCountMap, params.messages.length);
@@ -892,7 +903,9 @@ function createPruneMessages(factoryParams) {
892
903
  contextPressure,
893
904
  originalToolContent: originalToolContent.size > 0 ? originalToolContent : void 0,
894
905
  calibrationRatio,
895
- resolvedInstructionOverhead: bestInstructionOverhead
906
+ resolvedInstructionOverhead: bestInstructionOverhead,
907
+ contextBudget: pruningBudget,
908
+ effectiveInstructionTokens: currentInstructionTokens
896
909
  };
897
910
  const rawSpaceBudget = calibrationRatio > 0 ? Math.round(pruningBudget / calibrationRatio) : pruningBudget;
898
911
  const rawSpaceInstructionTokens = calibrationRatio > 0 ? Math.round(currentInstructionTokens / calibrationRatio) : currentInstructionTokens;
@@ -1085,7 +1098,10 @@ function createPruneMessages(factoryParams) {
1085
1098
  for (const [key, value] of Object.entries(preEmergencyTokenCounts)) indexTokenCountMap[key] = value;
1086
1099
  }
1087
1100
  }
1088
- const remainingContextTokens = Math.max(0, Math.min(pruningBudget, initialRemainingContextTokens + reclaimedTokens));
1101
+ /** Scale raw-space remaining back to calibrated/provider units so it is
1102
+ * directly comparable with pruningBudget and prePruneContextTokens */
1103
+ const rawRemaining = Math.max(0, initialRemainingContextTokens + reclaimedTokens);
1104
+ const remainingContextTokens = Math.max(0, Math.min(pruningBudget, calibrationRatio > 0 ? Math.round(rawRemaining * calibrationRatio) : rawRemaining));
1089
1105
  runThinkingStartIndex = thinkingStartIndex ?? -1;
1090
1106
  /** The index is the first value of `context`, index relative to `params.messages` */
1091
1107
  lastCutOffIndex = Math.max(params.messages.length - (context.length - (context[0]?.getType() === "system" ? 1 : 0)), 0);
@@ -1098,7 +1114,9 @@ function createPruneMessages(factoryParams) {
1098
1114
  contextPressure,
1099
1115
  originalToolContent: originalToolContent.size > 0 ? originalToolContent : void 0,
1100
1116
  calibrationRatio,
1101
- resolvedInstructionOverhead: bestInstructionOverhead
1117
+ resolvedInstructionOverhead: bestInstructionOverhead,
1118
+ contextBudget: pruningBudget,
1119
+ effectiveInstructionTokens: currentInstructionTokens
1102
1120
  };
1103
1121
  };
1104
1122
  }