@librechat/agents 3.2.36 → 3.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs +1 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +7 -8
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +16 -5
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +7 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +92 -3
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +24 -4
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/messages/cache.cjs +183 -0
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +1 -1
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +28 -14
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +2 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +8 -9
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +16 -5
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +7 -0
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +92 -3
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +24 -4
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/cache.mjs +182 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +2 -2
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +28 -14
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/types/messages/cache.d.ts +40 -0
- package/dist/types/types/graph.d.ts +2 -0
- package/package.json +2 -1
- package/src/agents/AgentContext.ts +2 -2
- package/src/agents/__tests__/AgentContext.test.ts +3 -9
- package/src/graphs/Graph.ts +65 -36
- package/src/langfuse.ts +38 -4
- package/src/langfuseToolOutputTracing.ts +18 -0
- package/src/llm/anthropic/utils/message_inputs.ts +131 -3
- package/src/llm/anthropic/utils/stripPrefillCache.test.ts +111 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +129 -0
- package/src/llm/bedrock/utils/message_inputs.ts +46 -4
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +103 -0
- package/src/messages/cache.tail.test.ts +340 -0
- package/src/messages/cache.ts +266 -0
- package/src/messages/tailCacheConversion.test.ts +161 -0
- package/src/scripts/bench-prompt-cache.ts +479 -0
- package/src/specs/langfuse-config.test.ts +69 -2
- package/src/specs/langfuse-metadata.test.ts +44 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +6 -0
- package/src/summarization/node.ts +2 -2
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +50 -0
- package/src/tools/toolOutputReferences.ts +34 -20
- package/src/types/graph.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.mjs","names":["chunkAny"],"sources":["../../../src/summarization/node.ts"],"sourcesContent":["import {\n AIMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n} from '@langchain/core/messages';\nimport type { UsageMetadata, BaseMessage } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type { HookRegistry } from '@/hooks';\nimport type { OnChunk } from '@/llm/invoke';\nimport type * as t from '@/types';\nimport {\n Constants,\n ContentTypes,\n GraphEvents,\n StepTypes,\n Providers,\n} from '@/common';\nimport { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';\nimport { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';\nimport { createRemoveAllMessage } from '@/messages/reducer';\nimport { splitAtRecencyBoundary } from '@/messages/recency';\nimport { getMaxOutputTokensKey } from '@/llm/request';\nimport { addCacheControl } from '@/messages/cache';\nimport { initializeModel } from '@/llm/init';\nimport { getChunkContent } from '@/stream';\nimport { executeHooks } from '@/hooks';\n\nconst SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);\n\n/**\n * Default number of recent user-led turns preserved verbatim during\n * compaction. A turn begins at a HumanMessage and includes every\n * following AIMessage and ToolMessage up to the next HumanMessage.\n * The most recent turn is always retained regardless of this value;\n * the default of `2` additionally keeps the prior exchange so the\n * model has fresh context on what just happened. Setting\n * `retainRecent.turns` to `0` reverts to the legacy \"summarize every\n * message\" behavior.\n */\nconst DEFAULT_RETAIN_RECENT_TURNS = 2;\n\n/**\n * Token overhead of the XML wrapper + instruction text added around the\n * summary at injection time in AgentContext.buildSystemRunnable:\n * `<summary>\\n${text}\\n</summary>\\n\\nYour context window was compacted...`\n * ~33 tokens on Anthropic, ~24-27 on OpenAI. Using 33 as a safe ceiling.\n */\nconst SUMMARY_WRAPPER_OVERHEAD_TOKENS = 33;\n\n/** Structured checkpoint prompt for fresh summarization (no prior summary). */\nexport const DEFAULT_SUMMARIZATION_PROMPT = `Hold on, before you continue I need you to write me a checkpoint of everything so far. Your context window is filling up and this checkpoint replaces the messages above, so capture everything you need to pick right back up.\n\nDon't second-guess or fact-check anything you did, your tool results reflect exactly what happened. If a tool result appears truncated, that's just a display artifact from context management: the tool executed fully. Just record what you did and what you observed. Only the checkpoint, don't respond to me or continue the conversation.\n\n## Checkpoint\n\n## Goal\nWhat I asked you to do and any sub-goals you identified.\n\n## Constraints & Preferences\nAny rules, preferences, or configuration I established.\n\n## Progress\n### Done\n- What you completed and the outcomes\n\n### In Progress\n- What you're currently working on\n\n## Key Decisions\nDecisions you made and why.\n\n## Next Steps\nConcrete task actions remaining, in priority order.\n\n## Critical Context\nExact identifiers, names, error messages, URLs, and details you need to preserve verbatim.\n\nRules:\n- Record what you did and observed, don't judge or re-evaluate it\n- For each tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Short declarative sentences\n- Skip empty sections`;\n\n/** Prompt for re-compaction when a prior summary exists. */\nexport const DEFAULT_UPDATE_SUMMARIZATION_PROMPT = `Hold on again, update your checkpoint. Merge the new messages into your existing checkpoint and give me a single consolidated replacement.\n\nKeep it roughly the same length as your last checkpoint. Compress older details to make room for what's new, don't just append. Give recent actions more detail, compress older items to one-liners.\n\nDon't fact-check or second-guess anything, your tool results are ground truth. If a tool result appears truncated, that's just a display artifact: the tool executed fully. Only the checkpoint, don't respond to me or continue the conversation.\n\nRules:\n- Merge new progress into existing sections, don't duplicate headers\n- Compress older completed items into one-line entries\n- Move items from \"In Progress\" to \"Done\" when you completed them\n- Update \"Next Steps\" to reflect current task priorities.\n- For each new tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Skip empty sections`;\n\nfunction separateParameters(parameters: Record<string, unknown>): {\n llmParams: Record<string, unknown>;\n maxSummaryTokens?: number;\n} {\n const llmParams: Record<string, unknown> = {};\n let maxSummaryTokens: number | undefined;\n\n for (const [key, value] of Object.entries(parameters)) {\n if (SUMMARIZATION_PARAM_KEYS.has(key)) {\n if (\n key === 'maxSummaryTokens' &&\n typeof value === 'number' &&\n value > 0\n ) {\n maxSummaryTokens = value;\n }\n } else {\n llmParams[key] = value;\n }\n }\n\n return { llmParams, maxSummaryTokens };\n}\n\n/**\n * Generates a structural metadata summary without making an LLM call.\n * Used as a last-resort fallback when all summarization attempts fail.\n * Preserves tool names and message counts so the agent retains basic context.\n */\nfunction generateMetadataStub(messages: BaseMessage[]): string {\n const counts: Record<string, number> = {};\n const toolNames = new Set<string>();\n\n for (const msg of messages) {\n const role = msg.getType();\n counts[role] = (counts[role] ?? 0) + 1;\n\n if (role === 'tool' && msg.name != null && msg.name !== '') {\n toolNames.add(msg.name);\n }\n\n if (\n role === 'ai' &&\n msg instanceof AIMessage &&\n msg.tool_calls &&\n msg.tool_calls.length > 0\n ) {\n for (const tc of msg.tool_calls) {\n toolNames.add(tc.name);\n }\n }\n }\n\n const countParts = Object.entries(counts)\n .map(([role, count]) => `${count} ${role}`)\n .join(', ');\n\n const lines = [\n `[Metadata summary: ${messages.length} messages (${countParts})]`,\n ];\n\n if (toolNames.size > 0) {\n lines.push(`[Tools used: ${Array.from(toolNames).join(', ')}]`);\n }\n\n return lines.join('\\n');\n}\n\n/** Maximum number of tool failures to include in the enrichment section. */\nconst MAX_TOOL_FAILURES = 8;\n/** Maximum chars per failure summary line. */\nconst MAX_TOOL_FAILURE_CHARS = 240;\n\n/**\n * Extracts failed tool results from messages and formats them as a structured\n * section. LLMs often omit specific failure details (exit codes, error messages)\n * from their summaries, this mechanical enrichment guarantees they survive.\n */\nfunction extractToolFailuresSection(messages: BaseMessage[]): string {\n const failures: Array<{ toolName: string; summary: string }> = [];\n const seen = new Set<string>();\n\n for (const msg of messages) {\n if (msg.getType() !== 'tool') {\n continue;\n }\n const toolMsg = msg as ToolMessage;\n if (toolMsg.status !== 'error') {\n continue;\n }\n // Deduplicate by tool_call_id\n const callId = toolMsg.tool_call_id;\n if (callId && seen.has(callId)) {\n continue;\n }\n if (callId) {\n seen.add(callId);\n }\n\n const toolName = toolMsg.name ?? 'tool';\n const content =\n typeof toolMsg.content === 'string'\n ? toolMsg.content\n : JSON.stringify(toolMsg.content);\n const normalized = content.replace(/\\s+/g, ' ').trim();\n const summary =\n normalized.length > MAX_TOOL_FAILURE_CHARS\n ? `${normalized.slice(0, MAX_TOOL_FAILURE_CHARS - 3)}...`\n : normalized;\n\n failures.push({ toolName, summary });\n }\n\n if (failures.length === 0) {\n return '';\n }\n\n const lines = failures\n .slice(0, MAX_TOOL_FAILURES)\n .map((f) => `- ${f.toolName}: ${f.summary}`);\n if (failures.length > MAX_TOOL_FAILURES) {\n lines.push(`- ...and ${failures.length - MAX_TOOL_FAILURES} more`);\n }\n\n return `\\n\\n## Tool Failures\\n${lines.join('\\n')}`;\n}\n\n/**\n * Appends mechanical enrichment sections to an LLM-generated summary.\n * Tool failures are appended verbatim because LLMs often omit specific\n * error details from their summaries.\n */\nfunction enrichSummary(summaryText: string, messages: BaseMessage[]): string {\n return summaryText + extractToolFailuresSection(messages);\n}\n\n/**\n * Restores pre-masking tool content onto the messages array using\n * `pendingOriginalToolContent` stored on AgentContext. Only allocates\n * a new array when there are entries to restore; otherwise returns the\n * input reference unchanged.\n */\nfunction restoreOriginalToolContent(\n messages: BaseMessage[],\n originalToolContent: Map<number, string> | undefined\n): BaseMessage[] {\n if (originalToolContent == null || originalToolContent.size === 0) {\n return messages;\n }\n const restored = [...messages];\n for (const [idx, content] of originalToolContent) {\n const msg = restored[idx];\n if (msg instanceof ToolMessage) {\n restored[idx] = new ToolMessage({\n content,\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n id: msg.id,\n additional_kwargs: msg.additional_kwargs,\n response_metadata: msg.response_metadata,\n });\n }\n }\n return restored;\n}\n\n// ---------------------------------------------------------------------------\n// Extracted helpers for createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface SummarizationClientConfig {\n provider: string;\n modelName?: string;\n clientOptions: Record<string, unknown>;\n effectiveMaxSummaryTokens?: number;\n promptText: string;\n updatePromptText: string;\n}\n\n/** Assembles the summarization model's client options from agent and config. */\nfunction buildSummarizationClientConfig(\n agentContext: AgentContext,\n summarizationConfig?: t.SummarizationConfig\n): SummarizationClientConfig {\n const provider = (summarizationConfig?.provider ??\n agentContext.provider) as string;\n const modelName = summarizationConfig?.model;\n const parameters = summarizationConfig?.parameters ?? {};\n const promptText =\n summarizationConfig?.prompt ?? DEFAULT_SUMMARIZATION_PROMPT;\n const updatePromptText =\n summarizationConfig?.updatePrompt ?? DEFAULT_UPDATE_SUMMARIZATION_PROMPT;\n\n const { llmParams, maxSummaryTokens: paramMaxSummaryTokens } =\n separateParameters(parameters);\n\n const isSelfSummarize = provider === (agentContext.provider as string);\n const baseOptions =\n isSelfSummarize && agentContext.clientOptions\n ? { ...agentContext.clientOptions }\n : {};\n\n const clientOptions: Record<string, unknown> = {\n ...baseOptions,\n ...llmParams,\n };\n\n if (modelName != null && modelName !== '') {\n clientOptions.model = modelName;\n clientOptions.modelName = modelName;\n }\n\n const effectiveMaxSummaryTokens =\n paramMaxSummaryTokens ?? summarizationConfig?.maxSummaryTokens;\n\n if (effectiveMaxSummaryTokens != null) {\n clientOptions[getMaxOutputTokensKey(provider)] = effectiveMaxSummaryTokens;\n }\n\n return {\n provider,\n modelName,\n clientOptions,\n effectiveMaxSummaryTokens,\n promptText,\n updatePromptText,\n };\n}\n\n/** Computes the token count for a summary, preferring provider output tokens when available. */\nfunction computeSummaryTokenCount(\n summaryText: string,\n summaryUsage: Partial<UsageMetadata> | undefined,\n tokenCounter?: (message: BaseMessage) => number\n): number {\n const providerOutputTokens = Number(summaryUsage?.output_tokens) || 0;\n if (providerOutputTokens > 0) {\n return providerOutputTokens + SUMMARY_WRAPPER_OVERHEAD_TOKENS;\n }\n if (tokenCounter) {\n return (\n tokenCounter(new SystemMessage(summaryText)) +\n SUMMARY_WRAPPER_OVERHEAD_TOKENS\n );\n }\n return 0;\n}\n\n/** Constructs the SummaryContentBlock persisted in the run step and dispatched to events. */\nfunction buildSummaryBlock(params: {\n summaryText: string;\n tokenCount: number;\n stepId: string;\n stepIndex: number;\n modelName?: string;\n provider: string;\n summaryVersion: number;\n}): t.SummaryContentBlock {\n return {\n type: ContentTypes.SUMMARY,\n content: [\n {\n type: ContentTypes.TEXT,\n text: params.summaryText,\n } as t.MessageContentComplex,\n ],\n tokenCount: params.tokenCount,\n summaryVersion: params.summaryVersion,\n boundary: {\n messageId: params.stepId,\n contentIndex: params.stepIndex,\n },\n model: params.modelName,\n provider: params.provider,\n createdAt: new Date().toISOString(),\n };\n}\n\ntype LogFn = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n data?: Record<string, unknown>\n) => void;\n\n/**\n * Extracts an HTTP status code from a thrown LLM-provider error. Returns\n * `undefined` for non-object values (including `null` or `undefined`, both\n * valid `throw` targets in JS) so callers never dereference a nullish\n * value.\n */\nfunction extractHttpStatus(err: unknown): number | undefined {\n if (err == null || typeof err !== 'object') {\n return undefined;\n }\n const errRecord = err as Record<string, unknown>;\n const direct = errRecord.status;\n if (typeof direct === 'number') {\n return direct;\n }\n const statusCode = errRecord.statusCode;\n if (typeof statusCode === 'number') {\n return statusCode;\n }\n const response = errRecord.response;\n if (response != null && typeof response === 'object') {\n const nested = (response as Record<string, unknown>).status;\n if (typeof nested === 'number') {\n return nested;\n }\n }\n return undefined;\n}\n\n/**\n * Formats a provider-level error for logging. Returns both a human-readable\n * suffix (safe to include in the message string so it survives any host-side\n * formatter) and a structured metadata bag for rich log backends.\n */\nfunction describeProviderError(\n err: unknown,\n provider: string,\n modelName?: string\n): { suffix: string; data: Record<string, unknown> } {\n const providerLabel = `${provider}/${modelName ?? '(no-model)'}`;\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const data: Record<string, unknown> = {\n provider,\n model: modelName,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${providerLabel}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Formats an exhausted-fallback error. `tryFallbackProviders` throws the\n * last fallback provider's error, which may be from any of the configured\n * fallbacks — not the primary — so we label the log with the list of\n * fallback providers attempted rather than mis-attributing to the primary.\n *\n * Entries in `fallbacks` are normally strongly typed, but we defend against\n * malformed runtime config (null/undefined entries, missing `provider`\n * field) so a recoverable summarization failure is never promoted to an\n * uncaught exception from inside the logging path.\n */\nfunction describeFallbackError(\n err: unknown,\n fallbacks: unknown\n): { suffix: string; data: Record<string, unknown> } {\n const errMsg = err instanceof Error ? err.message : String(err);\n const list: ReadonlyArray<unknown> = Array.isArray(fallbacks)\n ? fallbacks\n : [];\n const providerNames = list\n .map((f) => {\n if (f == null || typeof f !== 'object') {\n return undefined;\n }\n const raw = (f as { provider?: unknown }).provider;\n return raw != null ? String(raw) : undefined;\n })\n .filter((p): p is string => typeof p === 'string');\n const label =\n providerNames.length > 0\n ? `fallbacks=[${providerNames.join(',')}]`\n : 'no-fallbacks';\n\n const data: Record<string, unknown> = {\n fallbackProviders: providerNames,\n fallbackCount: list.length,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${label}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Runs the summarization LLM call with primary + fallback providers,\n * falling back to a metadata stub when all calls fail.\n */\nasync function executeSummarizationWithFallback(params: {\n agentContext: AgentContext;\n messages: BaseMessage[];\n clientConfig: SummarizationClientConfig;\n summarizeConfig?: RunnableConfig;\n stepId: string;\n usePromptCache: boolean;\n log: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const {\n agentContext,\n messages,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache,\n log,\n } = params;\n\n const priorSummaryText = agentContext.getSummaryText()?.trim() ?? '';\n\n let summaryText = '';\n let summaryUsage: Partial<UsageMetadata> | undefined;\n\n try {\n /**\n * Initialize inside the try so that a misconfigured provider\n * (e.g. an unrecognized summarization.provider) surfaces through the\n * `log('error', ...)` path below rather than bubbling up silently.\n */\n const summarizationModel = initializeModel({\n provider: clientConfig.provider as Providers,\n clientOptions: clientConfig.clientOptions as t.ClientOptions,\n tools: agentContext.getToolsForBinding(),\n }) as t.ChatModel;\n\n const result = await summarizeWithCacheHit({\n model: summarizationModel,\n messages,\n promptText: clientConfig.promptText,\n updatePromptText: clientConfig.updatePromptText,\n priorSummaryText,\n config: summarizeConfig,\n stepId,\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n usePromptCache,\n log,\n });\n summaryText = result.text;\n summaryUsage = result.usage;\n } catch (primaryError) {\n const primaryDescribed = describeProviderError(\n primaryError,\n clientConfig.provider,\n clientConfig.modelName\n );\n log('error', `Summarization LLM call failed ${primaryDescribed.suffix}`, {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n });\n\n const rawFallbacks = (\n clientConfig.clientOptions as unknown as t.LLMConfig | undefined\n )?.fallbacks;\n const fallbacks = Array.isArray(rawFallbacks) ? rawFallbacks : [];\n if (fallbacks.length > 0) {\n try {\n const onChunk = createSummarizationChunkHandler({\n stepId,\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n });\n const fbResult = await tryFallbackProviders({\n fallbacks,\n tools: agentContext.getToolsForBinding(),\n messages: [\n ...messages,\n new HumanMessage(\n buildSummarizationInstruction(\n clientConfig.promptText,\n clientConfig.updatePromptText,\n priorSummaryText\n )\n ),\n ],\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n primaryError,\n onChunk,\n });\n const fbMsg = fbResult?.messages?.[0];\n if (fbMsg) {\n summaryText = extractResponseText(\n fbMsg as { content: string | object }\n );\n }\n } catch (fbErr) {\n const fbDescribed = describeFallbackError(fbErr, fallbacks);\n log('warn', `Fallback providers also failed ${fbDescribed.suffix}`, {\n ...fbDescribed.data,\n });\n }\n }\n if (!summaryText) {\n log(\n 'warn',\n `Summarization failed, falling back to metadata stub ${primaryDescribed.suffix}`,\n {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n }\n );\n summaryText = generateMetadataStub(messages);\n }\n }\n\n return { text: summaryText, usage: summaryUsage };\n}\n\n/** Dispatches run step completion, ON_SUMMARIZE_COMPLETE, and rebuilds token map. */\nasync function dispatchCompletionEvents(params: {\n graph: CreateSummarizeNodeParams['graph'];\n runnableConfig?: RunnableConfig;\n stepId: string;\n summaryBlock: t.SummaryContentBlock;\n agentContext: AgentContext;\n runStep: t.RunStep;\n summaryUsage?: Partial<UsageMetadata>;\n agentId: string;\n /**\n * Number of messages preserved verbatim by the recency window after\n * compaction. Reported via the PostCompact hook payload so observers\n * (metrics, cleanup) see the true post-compaction message count\n * instead of always-zero.\n */\n messagesAfterCount: number;\n}): Promise<void> {\n const {\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId,\n messagesAfterCount,\n } = params;\n\n runStep.summary = summaryBlock;\n if (summaryUsage) {\n runStep.usage = {\n prompt_tokens: Number(summaryUsage.input_tokens) || 0,\n completion_tokens: Number(summaryUsage.output_tokens) || 0,\n total_tokens:\n (Number(summaryUsage.input_tokens) || 0) +\n (Number(summaryUsage.output_tokens) || 0),\n };\n }\n\n await graph.dispatchRunStepCompleted(\n stepId,\n { type: 'summary', summary: summaryBlock } satisfies t.SummaryCompleted,\n runnableConfig\n );\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId,\n summary: summaryBlock,\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PostCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n const firstBlock = summaryBlock.content?.[0];\n const summaryText =\n firstBlock != null &&\n typeof firstBlock === 'object' &&\n 'text' in firstBlock &&\n typeof firstBlock.text === 'string'\n ? firstBlock.text\n : '';\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PostCompact',\n runId: sessionId,\n threadId,\n agentId,\n summary: summaryText,\n messagesAfterCount,\n },\n sessionId,\n }).catch(() => {\n /* PostCompact is observational — swallow errors */\n });\n }\n\n agentContext.rebuildTokenMapAfterSummarization({});\n}\n\n// ---------------------------------------------------------------------------\n// createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface CreateSummarizeNodeParams {\n agentContext: AgentContext;\n graph: {\n contentData: t.RunStep[];\n contentIndexMap: Map<string, number>;\n config?: RunnableConfig;\n runId?: string;\n isMultiAgent: boolean;\n hookRegistry?: HookRegistry;\n dispatchRunStep: (\n runStep: t.RunStep,\n config?: RunnableConfig\n ) => Promise<void>;\n dispatchRunStepCompleted: (\n stepId: string,\n result: t.StepCompleted,\n config?: RunnableConfig\n ) => Promise<void>;\n };\n generateStepId: (stepKey: string) => [string, number];\n}\n\nexport function createSummarizeNode({\n agentContext,\n graph,\n generateStepId,\n}: CreateSummarizeNodeParams) {\n return async (\n state: {\n messages: BaseMessage[];\n summarizationRequest?: t.SummarizationNodeInput;\n },\n config?: RunnableConfig\n ): Promise<{ summarizationRequest: undefined; messages?: BaseMessage[] }> => {\n const request = state.summarizationRequest;\n if (request == null) {\n return { summarizationRequest: undefined };\n }\n\n const maxCtx = agentContext.maxContextTokens ?? 0;\n if (maxCtx > 0 && agentContext.instructionTokens >= maxCtx) {\n emitAgentLog(\n config,\n 'warn',\n 'summarize',\n 'Summarization skipped, instructions exceed context budget. Reduce the number of tools or increase maxContextTokens.',\n {\n instructionTokens: agentContext.instructionTokens,\n maxContextTokens: maxCtx,\n breakdown: agentContext.formatTokenBudgetBreakdown(),\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n return { summarizationRequest: undefined };\n }\n\n /**\n * Capture the original-tool-content map locally before doing the\n * split. We need it in three places: to restore the head for\n * summarizer quality, to leave intact on the skip path (state is\n * unchanged), and — critically — to carry forward the tail-relevant\n * entries on the summarize-fired path. Clearing it eagerly here\n * would lose the originals for masked tool messages that the\n * recency window keeps in the tail; a future summarization could\n * then only summarize the masked stub instead of the full payload.\n */\n const originalPending = agentContext.pendingOriginalToolContent;\n\n const restoredMessages = restoreOriginalToolContent(\n state.messages,\n originalPending\n );\n\n const runnableConfig = config ?? graph.config;\n\n const retainRecent = agentContext.summarizationConfig?.retainRecent;\n const { head: messagesToRefine, tailStartIndex } = splitAtRecencyBoundary(\n restoredMessages,\n {\n turns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n tokens: retainRecent?.tokens,\n tokenCounter: agentContext.tokenCounter,\n }\n );\n /**\n * Use the *masked* messages for the retained tail so that any\n * truncation prune applied to oversized ToolMessage content stays\n * truncated in live state. The summarizer above reads the restored\n * (full-content) head for summary quality, but reinjecting restored\n * tool payloads into state would defeat masking and bloat the\n * checkpoint, forcing more expensive re-pruning on later turns.\n * `restoreOriginalToolContent` returns an array with identical\n * length and structure to `state.messages` (replacements only at\n * specific indices), so the same tailStartIndex slices both arrays\n * at the same turn boundary.\n */\n const messagesToRetain = state.messages.slice(tailStartIndex);\n\n if (messagesToRefine.length === 0) {\n /**\n * Recency window covers the entire conversation — there is no\n * older content to summarize. Skipping prevents the model from\n * destroying the user's most recent message (e.g. a large pasted\n * payload on the first turn) by replacing it with a generic\n * checkpoint summary. Mark the trigger so the same unchanged\n * state is not re-evaluated on the next prune cycle.\n */\n emitAgentLog(\n config,\n 'debug',\n 'summarize',\n 'Summarization skipped — recency window retains all messages',\n {\n messagesRetained: messagesToRetain.length,\n retainTurns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n agentContext.markSummarizationTriggered(state.messages.length);\n return { summarizationRequest: undefined };\n }\n\n const clientConfig = buildSummarizationClientConfig(\n agentContext,\n agentContext.summarizationConfig\n );\n\n const stepKey = `summarize-${request.agentId}`;\n const [stepId, stepIndex] = generateStepId(stepKey);\n\n const placeholderSummary: t.SummaryContentBlock = {\n type: ContentTypes.SUMMARY,\n model: clientConfig.modelName,\n provider: clientConfig.provider,\n };\n\n const runStep: t.RunStep = {\n stepIndex,\n id: stepId,\n type: StepTypes.MESSAGE_CREATION,\n index: graph.contentData.length,\n stepDetails: {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: { message_id: stepId },\n },\n summary: placeholderSummary,\n usage: null,\n };\n\n if (graph.runId != null && graph.runId !== '') {\n runStep.runId = graph.runId;\n }\n if (graph.isMultiAgent && agentContext.agentId) {\n runStep.agentId = agentContext.agentId;\n }\n\n await graph.dispatchRunStep(runStep, runnableConfig);\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_START,\n {\n agentId: request.agentId,\n provider: clientConfig.provider,\n model: clientConfig.modelName,\n messagesToRefineCount: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion + 1,\n } satisfies t.SummarizeStartEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PreCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PreCompact',\n runId: sessionId,\n threadId,\n agentId: request.agentId,\n messagesBeforeCount: messagesToRefine.length,\n trigger: agentContext.summarizationConfig?.trigger?.type ?? 'default',\n },\n sessionId,\n }).catch(() => {\n /* PreCompact is observational — swallow errors */\n });\n }\n\n const isSelfSummarizeModel =\n clientConfig.provider === (agentContext.provider as string);\n const hasPromptCache =\n isSelfSummarizeModel &&\n (agentContext.clientOptions as Record<string, unknown> | undefined)\n ?.promptCache === true;\n\n const log: LogFn = (level, message, data) => {\n emitAgentLog(runnableConfig, level, 'summarize', message, data, {\n runId: graph.runId,\n agentId: request.agentId,\n });\n };\n\n log('debug', 'Summarization starting', {\n messagesToRefineCount: messagesToRefine.length,\n hasPriorSummary: (agentContext.getSummaryText()?.trim() ?? '') !== '',\n summaryVersion: agentContext.summaryVersion + 1,\n isSelfSummarize: isSelfSummarizeModel,\n hasPromptCache,\n provider: clientConfig.provider,\n });\n\n const summarizeConfig: RunnableConfig | undefined = config\n ? {\n ...config,\n metadata: {\n ...config.metadata,\n agent_id: request.agentId,\n summarization_provider: clientConfig.provider,\n summarization_model: clientConfig.modelName,\n /**\n * Per-call model attribution for usage consumers (the subagent\n * usage-capture handler): the summarizer's model can differ from\n * the agent's primary, and providers that emit no `ls_model_name`\n * would otherwise be billed against the primary config's model.\n * Omitted for self-summarize (no explicit model — the primary\n * config fallback is then correct). `tryFallbackProviders`\n * overrides this per fallback attempt; `INVOKED_PROVIDER` is\n * stamped by `attemptInvoke` itself.\n */\n ...(clientConfig.modelName != null && clientConfig.modelName !== ''\n ? { [Constants.INVOKED_MODEL]: clientConfig.modelName }\n : {}),\n },\n }\n : undefined;\n\n const { text: rawText, usage: summaryUsage } =\n await executeSummarizationWithFallback({\n agentContext,\n messages: messagesToRefine,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache: isSelfSummarizeModel && hasPromptCache,\n log,\n });\n\n if (!rawText) {\n agentContext.markSummarizationTriggered(0);\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId: request.agentId,\n error: 'Summarization produced empty output',\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n return { summarizationRequest: undefined };\n }\n\n const summaryText = enrichSummary(rawText, messagesToRefine);\n\n const tokenCount = computeSummaryTokenCount(\n summaryText,\n summaryUsage,\n agentContext.tokenCounter\n );\n\n agentContext.setSummary(summaryText, tokenCount);\n\n log('info', 'Summary persisted');\n log('debug', 'Summary details', {\n summaryTokens: tokenCount,\n textLength: summaryText.length,\n messagesCompacted: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion,\n ...(summaryUsage != null\n ? {\n input_tokens: summaryUsage.input_tokens,\n output_tokens: summaryUsage.output_tokens,\n cache_read: summaryUsage.input_token_details?.cache_read,\n cache_creation: summaryUsage.input_token_details?.cache_creation,\n }\n : {}),\n });\n\n const summaryBlock = buildSummaryBlock({\n summaryText,\n tokenCount,\n stepId,\n stepIndex: runStep.index,\n modelName: clientConfig.modelName,\n provider: clientConfig.provider,\n summaryVersion: agentContext.summaryVersion,\n });\n\n await dispatchCompletionEvents({\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId: request.agentId,\n messagesAfterCount: messagesToRetain.length,\n });\n\n /**\n * `dispatchCompletionEvents` calls `rebuildTokenMapAfterSummarization({})`\n * which resets the dedupe baseline to 0 — correct under the legacy\n * \"remove-all only\" shape where no messages survived, but stale once\n * the recency window keeps a tail. Realign the baseline to the\n * surviving tail length so a subsequent prune cycle on the unchanged\n * tail short-circuits via `shouldSkipSummarization` instead of\n * looping back into another summarize call.\n */\n agentContext.markSummarizationTriggered(messagesToRetain.length);\n\n /**\n * Carry forward the original-content entries that correspond to the\n * retained tail, reindexed for the post-removeAll state where tail\n * messages start at index 0. Without this, a future summarization\n * that pulls these tail messages into its head would only see the\n * masked stubs (since `setSummary` clears `pruneMessages`, and the\n * fresh pruner at the next turn has no record of prior masks).\n * Entries for indices < `tailStartIndex` belong to messages we just\n * summarized — they are no longer reachable so they are dropped.\n */\n if (originalPending != null && originalPending.size > 0) {\n const tailPending = new Map<number, string>();\n for (const [idx, content] of originalPending) {\n if (idx >= tailStartIndex) {\n tailPending.set(idx - tailStartIndex, content);\n }\n }\n agentContext.pendingOriginalToolContent =\n tailPending.size > 0 ? tailPending : undefined;\n } else {\n agentContext.pendingOriginalToolContent = undefined;\n }\n\n return {\n summarizationRequest: undefined,\n messages:\n messagesToRetain.length > 0\n ? [createRemoveAllMessage(), ...messagesToRetain]\n : [createRemoveAllMessage()],\n };\n };\n}\n\n/** Extracts text from an LLM response, skipping reasoning/thinking blocks. */\nfunction extractResponseText(response: { content: string | object }): string {\n const { content } = response;\n if (typeof content === 'string') {\n return content.trim();\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') {\n parts.push(block);\n continue;\n }\n if (block == null || typeof block !== 'object') {\n continue;\n }\n const rec = block as Record<string, unknown>;\n if (\n rec.type === ContentTypes.THINKING ||\n rec.type === ContentTypes.REASONING_CONTENT ||\n rec.type === 'redacted_thinking'\n ) {\n continue;\n }\n if (rec.type === 'text' && typeof rec.text === 'string') {\n parts.push(rec.text);\n }\n }\n return parts.join('').trim();\n}\n\nfunction buildSummarizationInstruction(\n promptText: string,\n updatePromptText: string | undefined,\n priorSummaryText: string\n): string {\n const effectivePrompt = priorSummaryText\n ? (updatePromptText ?? promptText)\n : promptText;\n const parts = [effectivePrompt];\n if (priorSummaryText) {\n parts.push(\n `\\n\\n<previous-summary>\\n${priorSummaryText}\\n</previous-summary>`\n );\n }\n return parts.join('');\n}\n\n/** Creates an `onChunk` callback that dispatches `ON_SUMMARIZE_DELTA` events for streaming. */\nfunction createSummarizationChunkHandler({\n stepId,\n config,\n provider,\n reasoningKey = 'reasoning_content',\n}: {\n stepId?: string;\n config?: RunnableConfig;\n provider?: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n}): OnChunk | undefined {\n if (stepId == null || stepId === '' || !config) {\n return undefined;\n }\n return (chunk) => {\n const chunkAny = chunk as Parameters<typeof getChunkContent>[0]['chunk'];\n const raw = getChunkContent({ chunk: chunkAny, provider, reasoningKey });\n if (raw == null || (typeof raw === 'string' && !raw)) {\n return;\n }\n const contentBlocks: t.MessageContentComplex[] =\n typeof raw === 'string'\n ? [{ type: ContentTypes.TEXT, text: raw } as t.MessageContentComplex]\n : raw;\n\n void safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_DELTA,\n {\n id: stepId,\n delta: {\n summary: {\n type: ContentTypes.SUMMARY,\n content: contentBlocks,\n provider: String(config.metadata?.summarization_provider ?? ''),\n model: String(config.metadata?.summarization_model ?? ''),\n },\n },\n } satisfies t.SummarizeDeltaEvent,\n config\n );\n };\n}\n\nfunction traceConfig(\n config: RunnableConfig | undefined,\n stage: string\n): RunnableConfig | undefined {\n if (!config) {\n return undefined;\n }\n return {\n ...config,\n runName: `summarization:${stage}`,\n metadata: { ...config.metadata, summarization: true, stage },\n };\n}\n\n/**\n * Cache-friendly compaction: sends raw conversation messages with the\n * summarization instruction appended as the final HumanMessage.\n * Providers with prompt caching get a cache hit on the system prompt +\n * tool definitions prefix.\n */\nasync function summarizeWithCacheHit({\n model,\n messages,\n promptText,\n updatePromptText,\n priorSummaryText,\n config,\n stepId,\n provider,\n reasoningKey,\n usePromptCache,\n log,\n}: {\n model: t.ChatModel;\n messages: BaseMessage[];\n promptText: string;\n updatePromptText?: string;\n priorSummaryText: string;\n config?: RunnableConfig;\n stepId?: string;\n provider: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n usePromptCache?: boolean;\n log?: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const instruction = buildSummarizationInstruction(\n promptText,\n updatePromptText,\n priorSummaryText\n );\n\n const fullMessages = [...messages, new HumanMessage(instruction)];\n const invokeMessages =\n usePromptCache === true ? addCacheControl(fullMessages) : fullMessages;\n\n const result = await attemptInvoke(\n {\n model,\n messages: invokeMessages,\n provider,\n onChunk: createSummarizationChunkHandler({\n stepId,\n config: traceConfig(config, 'cache_hit_compaction'),\n provider,\n reasoningKey,\n }),\n },\n traceConfig(config, 'cache_hit_compaction')\n );\n\n const responseMsg = result.messages?.[0];\n const text = responseMsg\n ? extractResponseText(responseMsg as { content: string | object })\n : '';\n let usage: Partial<UsageMetadata> | undefined;\n let usageSource = 'none';\n if (\n responseMsg != null &&\n 'usage_metadata' in responseMsg &&\n responseMsg.usage_metadata != null\n ) {\n usage = responseMsg.usage_metadata as Partial<UsageMetadata>;\n usageSource = 'usage_metadata';\n } else if (responseMsg != null) {\n const respMeta = responseMsg.response_metadata as\n | Record<string, unknown>\n | undefined;\n const raw = (respMeta?.metadata as Record<string, unknown> | undefined)\n ?.usage as Record<string, unknown> | undefined;\n if (raw != null) {\n usage = {\n input_tokens: Number(raw.inputTokens) || undefined,\n output_tokens: Number(raw.outputTokens) || undefined,\n } as Partial<UsageMetadata>;\n usageSource = 'response_metadata';\n }\n }\n const cacheDetails = (\n usage as\n | {\n input_token_details?: {\n cache_read?: number;\n cache_creation?: number;\n };\n }\n | undefined\n )?.input_token_details;\n log?.('debug', 'Summarization LLM usage', {\n source: usageSource,\n input_tokens: usage?.input_tokens,\n output_tokens: usage?.output_tokens,\n ...(cacheDetails?.cache_read != null || cacheDetails?.cache_creation != null\n ? {\n 'input_token_details.cache_read': cacheDetails.cache_read,\n 'input_token_details.cache_creation': cacheDetails.cache_creation,\n }\n : {}),\n });\n return { text, usage };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node.mjs","names":["chunkAny"],"sources":["../../../src/summarization/node.ts"],"sourcesContent":["import {\n AIMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n} from '@langchain/core/messages';\nimport type { UsageMetadata, BaseMessage } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type { HookRegistry } from '@/hooks';\nimport type { OnChunk } from '@/llm/invoke';\nimport type * as t from '@/types';\nimport {\n Constants,\n ContentTypes,\n GraphEvents,\n StepTypes,\n Providers,\n} from '@/common';\nimport { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';\nimport { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';\nimport { createRemoveAllMessage } from '@/messages/reducer';\nimport { splitAtRecencyBoundary } from '@/messages/recency';\nimport { getMaxOutputTokensKey } from '@/llm/request';\nimport { addTailCacheControl } from '@/messages/cache';\nimport { initializeModel } from '@/llm/init';\nimport { getChunkContent } from '@/stream';\nimport { executeHooks } from '@/hooks';\n\nconst SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);\n\n/**\n * Default number of recent user-led turns preserved verbatim during\n * compaction. A turn begins at a HumanMessage and includes every\n * following AIMessage and ToolMessage up to the next HumanMessage.\n * The most recent turn is always retained regardless of this value;\n * the default of `2` additionally keeps the prior exchange so the\n * model has fresh context on what just happened. Setting\n * `retainRecent.turns` to `0` reverts to the legacy \"summarize every\n * message\" behavior.\n */\nconst DEFAULT_RETAIN_RECENT_TURNS = 2;\n\n/**\n * Token overhead of the XML wrapper + instruction text added around the\n * summary at injection time in AgentContext.buildSystemRunnable:\n * `<summary>\\n${text}\\n</summary>\\n\\nYour context window was compacted...`\n * ~33 tokens on Anthropic, ~24-27 on OpenAI. Using 33 as a safe ceiling.\n */\nconst SUMMARY_WRAPPER_OVERHEAD_TOKENS = 33;\n\n/** Structured checkpoint prompt for fresh summarization (no prior summary). */\nexport const DEFAULT_SUMMARIZATION_PROMPT = `Hold on, before you continue I need you to write me a checkpoint of everything so far. Your context window is filling up and this checkpoint replaces the messages above, so capture everything you need to pick right back up.\n\nDon't second-guess or fact-check anything you did, your tool results reflect exactly what happened. If a tool result appears truncated, that's just a display artifact from context management: the tool executed fully. Just record what you did and what you observed. Only the checkpoint, don't respond to me or continue the conversation.\n\n## Checkpoint\n\n## Goal\nWhat I asked you to do and any sub-goals you identified.\n\n## Constraints & Preferences\nAny rules, preferences, or configuration I established.\n\n## Progress\n### Done\n- What you completed and the outcomes\n\n### In Progress\n- What you're currently working on\n\n## Key Decisions\nDecisions you made and why.\n\n## Next Steps\nConcrete task actions remaining, in priority order.\n\n## Critical Context\nExact identifiers, names, error messages, URLs, and details you need to preserve verbatim.\n\nRules:\n- Record what you did and observed, don't judge or re-evaluate it\n- For each tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Short declarative sentences\n- Skip empty sections`;\n\n/** Prompt for re-compaction when a prior summary exists. */\nexport const DEFAULT_UPDATE_SUMMARIZATION_PROMPT = `Hold on again, update your checkpoint. Merge the new messages into your existing checkpoint and give me a single consolidated replacement.\n\nKeep it roughly the same length as your last checkpoint. Compress older details to make room for what's new, don't just append. Give recent actions more detail, compress older items to one-liners.\n\nDon't fact-check or second-guess anything, your tool results are ground truth. If a tool result appears truncated, that's just a display artifact: the tool executed fully. Only the checkpoint, don't respond to me or continue the conversation.\n\nRules:\n- Merge new progress into existing sections, don't duplicate headers\n- Compress older completed items into one-line entries\n- Move items from \"In Progress\" to \"Done\" when you completed them\n- Update \"Next Steps\" to reflect current task priorities.\n- For each new tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Skip empty sections`;\n\nfunction separateParameters(parameters: Record<string, unknown>): {\n llmParams: Record<string, unknown>;\n maxSummaryTokens?: number;\n} {\n const llmParams: Record<string, unknown> = {};\n let maxSummaryTokens: number | undefined;\n\n for (const [key, value] of Object.entries(parameters)) {\n if (SUMMARIZATION_PARAM_KEYS.has(key)) {\n if (\n key === 'maxSummaryTokens' &&\n typeof value === 'number' &&\n value > 0\n ) {\n maxSummaryTokens = value;\n }\n } else {\n llmParams[key] = value;\n }\n }\n\n return { llmParams, maxSummaryTokens };\n}\n\n/**\n * Generates a structural metadata summary without making an LLM call.\n * Used as a last-resort fallback when all summarization attempts fail.\n * Preserves tool names and message counts so the agent retains basic context.\n */\nfunction generateMetadataStub(messages: BaseMessage[]): string {\n const counts: Record<string, number> = {};\n const toolNames = new Set<string>();\n\n for (const msg of messages) {\n const role = msg.getType();\n counts[role] = (counts[role] ?? 0) + 1;\n\n if (role === 'tool' && msg.name != null && msg.name !== '') {\n toolNames.add(msg.name);\n }\n\n if (\n role === 'ai' &&\n msg instanceof AIMessage &&\n msg.tool_calls &&\n msg.tool_calls.length > 0\n ) {\n for (const tc of msg.tool_calls) {\n toolNames.add(tc.name);\n }\n }\n }\n\n const countParts = Object.entries(counts)\n .map(([role, count]) => `${count} ${role}`)\n .join(', ');\n\n const lines = [\n `[Metadata summary: ${messages.length} messages (${countParts})]`,\n ];\n\n if (toolNames.size > 0) {\n lines.push(`[Tools used: ${Array.from(toolNames).join(', ')}]`);\n }\n\n return lines.join('\\n');\n}\n\n/** Maximum number of tool failures to include in the enrichment section. */\nconst MAX_TOOL_FAILURES = 8;\n/** Maximum chars per failure summary line. */\nconst MAX_TOOL_FAILURE_CHARS = 240;\n\n/**\n * Extracts failed tool results from messages and formats them as a structured\n * section. LLMs often omit specific failure details (exit codes, error messages)\n * from their summaries, this mechanical enrichment guarantees they survive.\n */\nfunction extractToolFailuresSection(messages: BaseMessage[]): string {\n const failures: Array<{ toolName: string; summary: string }> = [];\n const seen = new Set<string>();\n\n for (const msg of messages) {\n if (msg.getType() !== 'tool') {\n continue;\n }\n const toolMsg = msg as ToolMessage;\n if (toolMsg.status !== 'error') {\n continue;\n }\n // Deduplicate by tool_call_id\n const callId = toolMsg.tool_call_id;\n if (callId && seen.has(callId)) {\n continue;\n }\n if (callId) {\n seen.add(callId);\n }\n\n const toolName = toolMsg.name ?? 'tool';\n const content =\n typeof toolMsg.content === 'string'\n ? toolMsg.content\n : JSON.stringify(toolMsg.content);\n const normalized = content.replace(/\\s+/g, ' ').trim();\n const summary =\n normalized.length > MAX_TOOL_FAILURE_CHARS\n ? `${normalized.slice(0, MAX_TOOL_FAILURE_CHARS - 3)}...`\n : normalized;\n\n failures.push({ toolName, summary });\n }\n\n if (failures.length === 0) {\n return '';\n }\n\n const lines = failures\n .slice(0, MAX_TOOL_FAILURES)\n .map((f) => `- ${f.toolName}: ${f.summary}`);\n if (failures.length > MAX_TOOL_FAILURES) {\n lines.push(`- ...and ${failures.length - MAX_TOOL_FAILURES} more`);\n }\n\n return `\\n\\n## Tool Failures\\n${lines.join('\\n')}`;\n}\n\n/**\n * Appends mechanical enrichment sections to an LLM-generated summary.\n * Tool failures are appended verbatim because LLMs often omit specific\n * error details from their summaries.\n */\nfunction enrichSummary(summaryText: string, messages: BaseMessage[]): string {\n return summaryText + extractToolFailuresSection(messages);\n}\n\n/**\n * Restores pre-masking tool content onto the messages array using\n * `pendingOriginalToolContent` stored on AgentContext. Only allocates\n * a new array when there are entries to restore; otherwise returns the\n * input reference unchanged.\n */\nfunction restoreOriginalToolContent(\n messages: BaseMessage[],\n originalToolContent: Map<number, string> | undefined\n): BaseMessage[] {\n if (originalToolContent == null || originalToolContent.size === 0) {\n return messages;\n }\n const restored = [...messages];\n for (const [idx, content] of originalToolContent) {\n const msg = restored[idx];\n if (msg instanceof ToolMessage) {\n restored[idx] = new ToolMessage({\n content,\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n id: msg.id,\n additional_kwargs: msg.additional_kwargs,\n response_metadata: msg.response_metadata,\n });\n }\n }\n return restored;\n}\n\n// ---------------------------------------------------------------------------\n// Extracted helpers for createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface SummarizationClientConfig {\n provider: string;\n modelName?: string;\n clientOptions: Record<string, unknown>;\n effectiveMaxSummaryTokens?: number;\n promptText: string;\n updatePromptText: string;\n}\n\n/** Assembles the summarization model's client options from agent and config. */\nfunction buildSummarizationClientConfig(\n agentContext: AgentContext,\n summarizationConfig?: t.SummarizationConfig\n): SummarizationClientConfig {\n const provider = (summarizationConfig?.provider ??\n agentContext.provider) as string;\n const modelName = summarizationConfig?.model;\n const parameters = summarizationConfig?.parameters ?? {};\n const promptText =\n summarizationConfig?.prompt ?? DEFAULT_SUMMARIZATION_PROMPT;\n const updatePromptText =\n summarizationConfig?.updatePrompt ?? DEFAULT_UPDATE_SUMMARIZATION_PROMPT;\n\n const { llmParams, maxSummaryTokens: paramMaxSummaryTokens } =\n separateParameters(parameters);\n\n const isSelfSummarize = provider === (agentContext.provider as string);\n const baseOptions =\n isSelfSummarize && agentContext.clientOptions\n ? { ...agentContext.clientOptions }\n : {};\n\n const clientOptions: Record<string, unknown> = {\n ...baseOptions,\n ...llmParams,\n };\n\n if (modelName != null && modelName !== '') {\n clientOptions.model = modelName;\n clientOptions.modelName = modelName;\n }\n\n const effectiveMaxSummaryTokens =\n paramMaxSummaryTokens ?? summarizationConfig?.maxSummaryTokens;\n\n if (effectiveMaxSummaryTokens != null) {\n clientOptions[getMaxOutputTokensKey(provider)] = effectiveMaxSummaryTokens;\n }\n\n return {\n provider,\n modelName,\n clientOptions,\n effectiveMaxSummaryTokens,\n promptText,\n updatePromptText,\n };\n}\n\n/** Computes the token count for a summary, preferring provider output tokens when available. */\nfunction computeSummaryTokenCount(\n summaryText: string,\n summaryUsage: Partial<UsageMetadata> | undefined,\n tokenCounter?: (message: BaseMessage) => number\n): number {\n const providerOutputTokens = Number(summaryUsage?.output_tokens) || 0;\n if (providerOutputTokens > 0) {\n return providerOutputTokens + SUMMARY_WRAPPER_OVERHEAD_TOKENS;\n }\n if (tokenCounter) {\n return (\n tokenCounter(new SystemMessage(summaryText)) +\n SUMMARY_WRAPPER_OVERHEAD_TOKENS\n );\n }\n return 0;\n}\n\n/** Constructs the SummaryContentBlock persisted in the run step and dispatched to events. */\nfunction buildSummaryBlock(params: {\n summaryText: string;\n tokenCount: number;\n stepId: string;\n stepIndex: number;\n modelName?: string;\n provider: string;\n summaryVersion: number;\n}): t.SummaryContentBlock {\n return {\n type: ContentTypes.SUMMARY,\n content: [\n {\n type: ContentTypes.TEXT,\n text: params.summaryText,\n } as t.MessageContentComplex,\n ],\n tokenCount: params.tokenCount,\n summaryVersion: params.summaryVersion,\n boundary: {\n messageId: params.stepId,\n contentIndex: params.stepIndex,\n },\n model: params.modelName,\n provider: params.provider,\n createdAt: new Date().toISOString(),\n };\n}\n\ntype LogFn = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n data?: Record<string, unknown>\n) => void;\n\n/**\n * Extracts an HTTP status code from a thrown LLM-provider error. Returns\n * `undefined` for non-object values (including `null` or `undefined`, both\n * valid `throw` targets in JS) so callers never dereference a nullish\n * value.\n */\nfunction extractHttpStatus(err: unknown): number | undefined {\n if (err == null || typeof err !== 'object') {\n return undefined;\n }\n const errRecord = err as Record<string, unknown>;\n const direct = errRecord.status;\n if (typeof direct === 'number') {\n return direct;\n }\n const statusCode = errRecord.statusCode;\n if (typeof statusCode === 'number') {\n return statusCode;\n }\n const response = errRecord.response;\n if (response != null && typeof response === 'object') {\n const nested = (response as Record<string, unknown>).status;\n if (typeof nested === 'number') {\n return nested;\n }\n }\n return undefined;\n}\n\n/**\n * Formats a provider-level error for logging. Returns both a human-readable\n * suffix (safe to include in the message string so it survives any host-side\n * formatter) and a structured metadata bag for rich log backends.\n */\nfunction describeProviderError(\n err: unknown,\n provider: string,\n modelName?: string\n): { suffix: string; data: Record<string, unknown> } {\n const providerLabel = `${provider}/${modelName ?? '(no-model)'}`;\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const data: Record<string, unknown> = {\n provider,\n model: modelName,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${providerLabel}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Formats an exhausted-fallback error. `tryFallbackProviders` throws the\n * last fallback provider's error, which may be from any of the configured\n * fallbacks — not the primary — so we label the log with the list of\n * fallback providers attempted rather than mis-attributing to the primary.\n *\n * Entries in `fallbacks` are normally strongly typed, but we defend against\n * malformed runtime config (null/undefined entries, missing `provider`\n * field) so a recoverable summarization failure is never promoted to an\n * uncaught exception from inside the logging path.\n */\nfunction describeFallbackError(\n err: unknown,\n fallbacks: unknown\n): { suffix: string; data: Record<string, unknown> } {\n const errMsg = err instanceof Error ? err.message : String(err);\n const list: ReadonlyArray<unknown> = Array.isArray(fallbacks)\n ? fallbacks\n : [];\n const providerNames = list\n .map((f) => {\n if (f == null || typeof f !== 'object') {\n return undefined;\n }\n const raw = (f as { provider?: unknown }).provider;\n return raw != null ? String(raw) : undefined;\n })\n .filter((p): p is string => typeof p === 'string');\n const label =\n providerNames.length > 0\n ? `fallbacks=[${providerNames.join(',')}]`\n : 'no-fallbacks';\n\n const data: Record<string, unknown> = {\n fallbackProviders: providerNames,\n fallbackCount: list.length,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${label}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Runs the summarization LLM call with primary + fallback providers,\n * falling back to a metadata stub when all calls fail.\n */\nasync function executeSummarizationWithFallback(params: {\n agentContext: AgentContext;\n messages: BaseMessage[];\n clientConfig: SummarizationClientConfig;\n summarizeConfig?: RunnableConfig;\n stepId: string;\n usePromptCache: boolean;\n log: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const {\n agentContext,\n messages,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache,\n log,\n } = params;\n\n const priorSummaryText = agentContext.getSummaryText()?.trim() ?? '';\n\n let summaryText = '';\n let summaryUsage: Partial<UsageMetadata> | undefined;\n\n try {\n /**\n * Initialize inside the try so that a misconfigured provider\n * (e.g. an unrecognized summarization.provider) surfaces through the\n * `log('error', ...)` path below rather than bubbling up silently.\n */\n const summarizationModel = initializeModel({\n provider: clientConfig.provider as Providers,\n clientOptions: clientConfig.clientOptions as t.ClientOptions,\n tools: agentContext.getToolsForBinding(),\n }) as t.ChatModel;\n\n const result = await summarizeWithCacheHit({\n model: summarizationModel,\n messages,\n promptText: clientConfig.promptText,\n updatePromptText: clientConfig.updatePromptText,\n priorSummaryText,\n config: summarizeConfig,\n stepId,\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n usePromptCache,\n log,\n });\n summaryText = result.text;\n summaryUsage = result.usage;\n } catch (primaryError) {\n const primaryDescribed = describeProviderError(\n primaryError,\n clientConfig.provider,\n clientConfig.modelName\n );\n log('error', `Summarization LLM call failed ${primaryDescribed.suffix}`, {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n });\n\n const rawFallbacks = (\n clientConfig.clientOptions as unknown as t.LLMConfig | undefined\n )?.fallbacks;\n const fallbacks = Array.isArray(rawFallbacks) ? rawFallbacks : [];\n if (fallbacks.length > 0) {\n try {\n const onChunk = createSummarizationChunkHandler({\n stepId,\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n });\n const fbResult = await tryFallbackProviders({\n fallbacks,\n tools: agentContext.getToolsForBinding(),\n messages: [\n ...messages,\n new HumanMessage(\n buildSummarizationInstruction(\n clientConfig.promptText,\n clientConfig.updatePromptText,\n priorSummaryText\n )\n ),\n ],\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n primaryError,\n onChunk,\n });\n const fbMsg = fbResult?.messages?.[0];\n if (fbMsg) {\n summaryText = extractResponseText(\n fbMsg as { content: string | object }\n );\n }\n } catch (fbErr) {\n const fbDescribed = describeFallbackError(fbErr, fallbacks);\n log('warn', `Fallback providers also failed ${fbDescribed.suffix}`, {\n ...fbDescribed.data,\n });\n }\n }\n if (!summaryText) {\n log(\n 'warn',\n `Summarization failed, falling back to metadata stub ${primaryDescribed.suffix}`,\n {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n }\n );\n summaryText = generateMetadataStub(messages);\n }\n }\n\n return { text: summaryText, usage: summaryUsage };\n}\n\n/** Dispatches run step completion, ON_SUMMARIZE_COMPLETE, and rebuilds token map. */\nasync function dispatchCompletionEvents(params: {\n graph: CreateSummarizeNodeParams['graph'];\n runnableConfig?: RunnableConfig;\n stepId: string;\n summaryBlock: t.SummaryContentBlock;\n agentContext: AgentContext;\n runStep: t.RunStep;\n summaryUsage?: Partial<UsageMetadata>;\n agentId: string;\n /**\n * Number of messages preserved verbatim by the recency window after\n * compaction. Reported via the PostCompact hook payload so observers\n * (metrics, cleanup) see the true post-compaction message count\n * instead of always-zero.\n */\n messagesAfterCount: number;\n}): Promise<void> {\n const {\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId,\n messagesAfterCount,\n } = params;\n\n runStep.summary = summaryBlock;\n if (summaryUsage) {\n runStep.usage = {\n prompt_tokens: Number(summaryUsage.input_tokens) || 0,\n completion_tokens: Number(summaryUsage.output_tokens) || 0,\n total_tokens:\n (Number(summaryUsage.input_tokens) || 0) +\n (Number(summaryUsage.output_tokens) || 0),\n };\n }\n\n await graph.dispatchRunStepCompleted(\n stepId,\n { type: 'summary', summary: summaryBlock } satisfies t.SummaryCompleted,\n runnableConfig\n );\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId,\n summary: summaryBlock,\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PostCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n const firstBlock = summaryBlock.content?.[0];\n const summaryText =\n firstBlock != null &&\n typeof firstBlock === 'object' &&\n 'text' in firstBlock &&\n typeof firstBlock.text === 'string'\n ? firstBlock.text\n : '';\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PostCompact',\n runId: sessionId,\n threadId,\n agentId,\n summary: summaryText,\n messagesAfterCount,\n },\n sessionId,\n }).catch(() => {\n /* PostCompact is observational — swallow errors */\n });\n }\n\n agentContext.rebuildTokenMapAfterSummarization({});\n}\n\n// ---------------------------------------------------------------------------\n// createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface CreateSummarizeNodeParams {\n agentContext: AgentContext;\n graph: {\n contentData: t.RunStep[];\n contentIndexMap: Map<string, number>;\n config?: RunnableConfig;\n runId?: string;\n isMultiAgent: boolean;\n hookRegistry?: HookRegistry;\n dispatchRunStep: (\n runStep: t.RunStep,\n config?: RunnableConfig\n ) => Promise<void>;\n dispatchRunStepCompleted: (\n stepId: string,\n result: t.StepCompleted,\n config?: RunnableConfig\n ) => Promise<void>;\n };\n generateStepId: (stepKey: string) => [string, number];\n}\n\nexport function createSummarizeNode({\n agentContext,\n graph,\n generateStepId,\n}: CreateSummarizeNodeParams) {\n return async (\n state: {\n messages: BaseMessage[];\n summarizationRequest?: t.SummarizationNodeInput;\n },\n config?: RunnableConfig\n ): Promise<{ summarizationRequest: undefined; messages?: BaseMessage[] }> => {\n const request = state.summarizationRequest;\n if (request == null) {\n return { summarizationRequest: undefined };\n }\n\n const maxCtx = agentContext.maxContextTokens ?? 0;\n if (maxCtx > 0 && agentContext.instructionTokens >= maxCtx) {\n emitAgentLog(\n config,\n 'warn',\n 'summarize',\n 'Summarization skipped, instructions exceed context budget. Reduce the number of tools or increase maxContextTokens.',\n {\n instructionTokens: agentContext.instructionTokens,\n maxContextTokens: maxCtx,\n breakdown: agentContext.formatTokenBudgetBreakdown(),\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n return { summarizationRequest: undefined };\n }\n\n /**\n * Capture the original-tool-content map locally before doing the\n * split. We need it in three places: to restore the head for\n * summarizer quality, to leave intact on the skip path (state is\n * unchanged), and — critically — to carry forward the tail-relevant\n * entries on the summarize-fired path. Clearing it eagerly here\n * would lose the originals for masked tool messages that the\n * recency window keeps in the tail; a future summarization could\n * then only summarize the masked stub instead of the full payload.\n */\n const originalPending = agentContext.pendingOriginalToolContent;\n\n const restoredMessages = restoreOriginalToolContent(\n state.messages,\n originalPending\n );\n\n const runnableConfig = config ?? graph.config;\n\n const retainRecent = agentContext.summarizationConfig?.retainRecent;\n const { head: messagesToRefine, tailStartIndex } = splitAtRecencyBoundary(\n restoredMessages,\n {\n turns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n tokens: retainRecent?.tokens,\n tokenCounter: agentContext.tokenCounter,\n }\n );\n /**\n * Use the *masked* messages for the retained tail so that any\n * truncation prune applied to oversized ToolMessage content stays\n * truncated in live state. The summarizer above reads the restored\n * (full-content) head for summary quality, but reinjecting restored\n * tool payloads into state would defeat masking and bloat the\n * checkpoint, forcing more expensive re-pruning on later turns.\n * `restoreOriginalToolContent` returns an array with identical\n * length and structure to `state.messages` (replacements only at\n * specific indices), so the same tailStartIndex slices both arrays\n * at the same turn boundary.\n */\n const messagesToRetain = state.messages.slice(tailStartIndex);\n\n if (messagesToRefine.length === 0) {\n /**\n * Recency window covers the entire conversation — there is no\n * older content to summarize. Skipping prevents the model from\n * destroying the user's most recent message (e.g. a large pasted\n * payload on the first turn) by replacing it with a generic\n * checkpoint summary. Mark the trigger so the same unchanged\n * state is not re-evaluated on the next prune cycle.\n */\n emitAgentLog(\n config,\n 'debug',\n 'summarize',\n 'Summarization skipped — recency window retains all messages',\n {\n messagesRetained: messagesToRetain.length,\n retainTurns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n agentContext.markSummarizationTriggered(state.messages.length);\n return { summarizationRequest: undefined };\n }\n\n const clientConfig = buildSummarizationClientConfig(\n agentContext,\n agentContext.summarizationConfig\n );\n\n const stepKey = `summarize-${request.agentId}`;\n const [stepId, stepIndex] = generateStepId(stepKey);\n\n const placeholderSummary: t.SummaryContentBlock = {\n type: ContentTypes.SUMMARY,\n model: clientConfig.modelName,\n provider: clientConfig.provider,\n };\n\n const runStep: t.RunStep = {\n stepIndex,\n id: stepId,\n type: StepTypes.MESSAGE_CREATION,\n index: graph.contentData.length,\n stepDetails: {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: { message_id: stepId },\n },\n summary: placeholderSummary,\n usage: null,\n };\n\n if (graph.runId != null && graph.runId !== '') {\n runStep.runId = graph.runId;\n }\n if (graph.isMultiAgent && agentContext.agentId) {\n runStep.agentId = agentContext.agentId;\n }\n\n await graph.dispatchRunStep(runStep, runnableConfig);\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_START,\n {\n agentId: request.agentId,\n provider: clientConfig.provider,\n model: clientConfig.modelName,\n messagesToRefineCount: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion + 1,\n } satisfies t.SummarizeStartEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PreCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PreCompact',\n runId: sessionId,\n threadId,\n agentId: request.agentId,\n messagesBeforeCount: messagesToRefine.length,\n trigger: agentContext.summarizationConfig?.trigger?.type ?? 'default',\n },\n sessionId,\n }).catch(() => {\n /* PreCompact is observational — swallow errors */\n });\n }\n\n const isSelfSummarizeModel =\n clientConfig.provider === (agentContext.provider as string);\n const hasPromptCache =\n isSelfSummarizeModel &&\n (agentContext.clientOptions as Record<string, unknown> | undefined)\n ?.promptCache === true;\n\n const log: LogFn = (level, message, data) => {\n emitAgentLog(runnableConfig, level, 'summarize', message, data, {\n runId: graph.runId,\n agentId: request.agentId,\n });\n };\n\n log('debug', 'Summarization starting', {\n messagesToRefineCount: messagesToRefine.length,\n hasPriorSummary: (agentContext.getSummaryText()?.trim() ?? '') !== '',\n summaryVersion: agentContext.summaryVersion + 1,\n isSelfSummarize: isSelfSummarizeModel,\n hasPromptCache,\n provider: clientConfig.provider,\n });\n\n const summarizeConfig: RunnableConfig | undefined = config\n ? {\n ...config,\n metadata: {\n ...config.metadata,\n agent_id: request.agentId,\n summarization_provider: clientConfig.provider,\n summarization_model: clientConfig.modelName,\n /**\n * Per-call model attribution for usage consumers (the subagent\n * usage-capture handler): the summarizer's model can differ from\n * the agent's primary, and providers that emit no `ls_model_name`\n * would otherwise be billed against the primary config's model.\n * Omitted for self-summarize (no explicit model — the primary\n * config fallback is then correct). `tryFallbackProviders`\n * overrides this per fallback attempt; `INVOKED_PROVIDER` is\n * stamped by `attemptInvoke` itself.\n */\n ...(clientConfig.modelName != null && clientConfig.modelName !== ''\n ? { [Constants.INVOKED_MODEL]: clientConfig.modelName }\n : {}),\n },\n }\n : undefined;\n\n const { text: rawText, usage: summaryUsage } =\n await executeSummarizationWithFallback({\n agentContext,\n messages: messagesToRefine,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache: isSelfSummarizeModel && hasPromptCache,\n log,\n });\n\n if (!rawText) {\n agentContext.markSummarizationTriggered(0);\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId: request.agentId,\n error: 'Summarization produced empty output',\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n return { summarizationRequest: undefined };\n }\n\n const summaryText = enrichSummary(rawText, messagesToRefine);\n\n const tokenCount = computeSummaryTokenCount(\n summaryText,\n summaryUsage,\n agentContext.tokenCounter\n );\n\n agentContext.setSummary(summaryText, tokenCount);\n\n log('info', 'Summary persisted');\n log('debug', 'Summary details', {\n summaryTokens: tokenCount,\n textLength: summaryText.length,\n messagesCompacted: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion,\n ...(summaryUsage != null\n ? {\n input_tokens: summaryUsage.input_tokens,\n output_tokens: summaryUsage.output_tokens,\n cache_read: summaryUsage.input_token_details?.cache_read,\n cache_creation: summaryUsage.input_token_details?.cache_creation,\n }\n : {}),\n });\n\n const summaryBlock = buildSummaryBlock({\n summaryText,\n tokenCount,\n stepId,\n stepIndex: runStep.index,\n modelName: clientConfig.modelName,\n provider: clientConfig.provider,\n summaryVersion: agentContext.summaryVersion,\n });\n\n await dispatchCompletionEvents({\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId: request.agentId,\n messagesAfterCount: messagesToRetain.length,\n });\n\n /**\n * `dispatchCompletionEvents` calls `rebuildTokenMapAfterSummarization({})`\n * which resets the dedupe baseline to 0 — correct under the legacy\n * \"remove-all only\" shape where no messages survived, but stale once\n * the recency window keeps a tail. Realign the baseline to the\n * surviving tail length so a subsequent prune cycle on the unchanged\n * tail short-circuits via `shouldSkipSummarization` instead of\n * looping back into another summarize call.\n */\n agentContext.markSummarizationTriggered(messagesToRetain.length);\n\n /**\n * Carry forward the original-content entries that correspond to the\n * retained tail, reindexed for the post-removeAll state where tail\n * messages start at index 0. Without this, a future summarization\n * that pulls these tail messages into its head would only see the\n * masked stubs (since `setSummary` clears `pruneMessages`, and the\n * fresh pruner at the next turn has no record of prior masks).\n * Entries for indices < `tailStartIndex` belong to messages we just\n * summarized — they are no longer reachable so they are dropped.\n */\n if (originalPending != null && originalPending.size > 0) {\n const tailPending = new Map<number, string>();\n for (const [idx, content] of originalPending) {\n if (idx >= tailStartIndex) {\n tailPending.set(idx - tailStartIndex, content);\n }\n }\n agentContext.pendingOriginalToolContent =\n tailPending.size > 0 ? tailPending : undefined;\n } else {\n agentContext.pendingOriginalToolContent = undefined;\n }\n\n return {\n summarizationRequest: undefined,\n messages:\n messagesToRetain.length > 0\n ? [createRemoveAllMessage(), ...messagesToRetain]\n : [createRemoveAllMessage()],\n };\n };\n}\n\n/** Extracts text from an LLM response, skipping reasoning/thinking blocks. */\nfunction extractResponseText(response: { content: string | object }): string {\n const { content } = response;\n if (typeof content === 'string') {\n return content.trim();\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') {\n parts.push(block);\n continue;\n }\n if (block == null || typeof block !== 'object') {\n continue;\n }\n const rec = block as Record<string, unknown>;\n if (\n rec.type === ContentTypes.THINKING ||\n rec.type === ContentTypes.REASONING_CONTENT ||\n rec.type === 'redacted_thinking'\n ) {\n continue;\n }\n if (rec.type === 'text' && typeof rec.text === 'string') {\n parts.push(rec.text);\n }\n }\n return parts.join('').trim();\n}\n\nfunction buildSummarizationInstruction(\n promptText: string,\n updatePromptText: string | undefined,\n priorSummaryText: string\n): string {\n const effectivePrompt = priorSummaryText\n ? (updatePromptText ?? promptText)\n : promptText;\n const parts = [effectivePrompt];\n if (priorSummaryText) {\n parts.push(\n `\\n\\n<previous-summary>\\n${priorSummaryText}\\n</previous-summary>`\n );\n }\n return parts.join('');\n}\n\n/** Creates an `onChunk` callback that dispatches `ON_SUMMARIZE_DELTA` events for streaming. */\nfunction createSummarizationChunkHandler({\n stepId,\n config,\n provider,\n reasoningKey = 'reasoning_content',\n}: {\n stepId?: string;\n config?: RunnableConfig;\n provider?: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n}): OnChunk | undefined {\n if (stepId == null || stepId === '' || !config) {\n return undefined;\n }\n return (chunk) => {\n const chunkAny = chunk as Parameters<typeof getChunkContent>[0]['chunk'];\n const raw = getChunkContent({ chunk: chunkAny, provider, reasoningKey });\n if (raw == null || (typeof raw === 'string' && !raw)) {\n return;\n }\n const contentBlocks: t.MessageContentComplex[] =\n typeof raw === 'string'\n ? [{ type: ContentTypes.TEXT, text: raw } as t.MessageContentComplex]\n : raw;\n\n void safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_DELTA,\n {\n id: stepId,\n delta: {\n summary: {\n type: ContentTypes.SUMMARY,\n content: contentBlocks,\n provider: String(config.metadata?.summarization_provider ?? ''),\n model: String(config.metadata?.summarization_model ?? ''),\n },\n },\n } satisfies t.SummarizeDeltaEvent,\n config\n );\n };\n}\n\nfunction traceConfig(\n config: RunnableConfig | undefined,\n stage: string\n): RunnableConfig | undefined {\n if (!config) {\n return undefined;\n }\n return {\n ...config,\n runName: `summarization:${stage}`,\n metadata: { ...config.metadata, summarization: true, stage },\n };\n}\n\n/**\n * Cache-friendly compaction: sends raw conversation messages with the\n * summarization instruction appended as the final HumanMessage.\n * Providers with prompt caching get a cache hit on the system prompt +\n * tool definitions prefix.\n */\nasync function summarizeWithCacheHit({\n model,\n messages,\n promptText,\n updatePromptText,\n priorSummaryText,\n config,\n stepId,\n provider,\n reasoningKey,\n usePromptCache,\n log,\n}: {\n model: t.ChatModel;\n messages: BaseMessage[];\n promptText: string;\n updatePromptText?: string;\n priorSummaryText: string;\n config?: RunnableConfig;\n stepId?: string;\n provider: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n usePromptCache?: boolean;\n log?: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const instruction = buildSummarizationInstruction(\n promptText,\n updatePromptText,\n priorSummaryText\n );\n\n const fullMessages = [...messages, new HumanMessage(instruction)];\n const invokeMessages =\n usePromptCache === true ? addTailCacheControl(fullMessages) : fullMessages;\n\n const result = await attemptInvoke(\n {\n model,\n messages: invokeMessages,\n provider,\n onChunk: createSummarizationChunkHandler({\n stepId,\n config: traceConfig(config, 'cache_hit_compaction'),\n provider,\n reasoningKey,\n }),\n },\n traceConfig(config, 'cache_hit_compaction')\n );\n\n const responseMsg = result.messages?.[0];\n const text = responseMsg\n ? extractResponseText(responseMsg as { content: string | object })\n : '';\n let usage: Partial<UsageMetadata> | undefined;\n let usageSource = 'none';\n if (\n responseMsg != null &&\n 'usage_metadata' in responseMsg &&\n responseMsg.usage_metadata != null\n ) {\n usage = responseMsg.usage_metadata as Partial<UsageMetadata>;\n usageSource = 'usage_metadata';\n } else if (responseMsg != null) {\n const respMeta = responseMsg.response_metadata as\n | Record<string, unknown>\n | undefined;\n const raw = (respMeta?.metadata as Record<string, unknown> | undefined)\n ?.usage as Record<string, unknown> | undefined;\n if (raw != null) {\n usage = {\n input_tokens: Number(raw.inputTokens) || undefined,\n output_tokens: Number(raw.outputTokens) || undefined,\n } as Partial<UsageMetadata>;\n usageSource = 'response_metadata';\n }\n }\n const cacheDetails = (\n usage as\n | {\n input_token_details?: {\n cache_read?: number;\n cache_creation?: number;\n };\n }\n | undefined\n )?.input_token_details;\n log?.('debug', 'Summarization LLM usage', {\n source: usageSource,\n input_tokens: usage?.input_tokens,\n output_tokens: usage?.output_tokens,\n ...(cacheDetails?.cache_read != null || cacheDetails?.cache_creation != null\n ? {\n 'input_token_details.cache_read': cacheDetails.cache_read,\n 'input_token_details.cache_creation': cacheDetails.cache_creation,\n }\n : {}),\n });\n return { text, usage };\n}\n"],"mappings":"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"}
|
|
@@ -466,20 +466,34 @@ function annotateMessagesForLLM(messages, registry, runId) {
|
|
|
466
466
|
const tm = m;
|
|
467
467
|
let nextContent = tm.content;
|
|
468
468
|
if (annotates && typeof tm.content === "string") nextContent = annotateToolOutputWithReference(tm.content, liveRef, unresolved);
|
|
469
|
-
else if (annotates && Array.isArray(tm.content)
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
469
|
+
else if (annotates && Array.isArray(tm.content)) {
|
|
470
|
+
/**
|
|
471
|
+
* Array tool content. The string annotator can't run — this notably
|
|
472
|
+
* includes a tail tool result that prompt caching rewrote from a string
|
|
473
|
+
* into a text-block array to host its `cache_control` / `cachePoint`
|
|
474
|
+
* marker (the `_refKey` survives on `additional_kwargs`). Project the
|
|
475
|
+
* same markers the string path would, as leading text blocks: the live
|
|
476
|
+
* `[ref: …]` prefix and/or the unresolved-refs warning. Without this the
|
|
477
|
+
* common tool-result tail loses its reference marker once cached.
|
|
478
|
+
*
|
|
479
|
+
* `as unknown as ToolMessage['content']` is unavoidable: LangChain's
|
|
480
|
+
* content union does not accept a freshly built mixed array literal even
|
|
481
|
+
* though the structural shape is valid at runtime. The double-cast is
|
|
482
|
+
* structurally safe — every original block is preserved and only
|
|
483
|
+
* `{ type: 'text', text }` blocks (which all providers accept) are
|
|
484
|
+
* prepended.
|
|
485
|
+
*/
|
|
486
|
+
const prefixBlocks = [];
|
|
487
|
+
if (liveRef != null) prefixBlocks.push({
|
|
488
|
+
type: "text",
|
|
489
|
+
text: buildReferencePrefix(liveRef)
|
|
490
|
+
});
|
|
491
|
+
if (unresolved.length > 0) prefixBlocks.push({
|
|
492
|
+
type: "text",
|
|
493
|
+
text: `[unresolved refs: ${unresolved.join(", ")}]`
|
|
494
|
+
});
|
|
495
|
+
if (prefixBlocks.length > 0) nextContent = [...prefixBlocks, ...tm.content];
|
|
496
|
+
}
|
|
483
497
|
/**
|
|
484
498
|
* Project unconditionally: even when no annotation applies (stale
|
|
485
499
|
* `_refKey` or non-annotatable content), `cloneToolMessageWithContent`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolOutputReferences.mjs","names":[],"sources":["../../../src/tools/toolOutputReferences.ts"],"sourcesContent":["/**\n * Tool output reference registry.\n *\n * When enabled via `RunConfig.toolOutputReferences.enabled`, ToolNode\n * stores each successful tool output under a stable key\n * (`tool<idx>turn<turn>`) where `idx` is the tool's position within a\n * ToolNode batch and `turn` is the batch index within the run\n * (incremented once per ToolNode invocation).\n *\n * Subsequent tool calls can pipe a previous output into their args by\n * embedding `{{tool<idx>turn<turn>}}` inside any string argument;\n * {@link ToolOutputReferenceRegistry.resolve} walks the args and\n * substitutes the placeholders immediately before invocation.\n *\n * The registry stores the *raw, untruncated* tool output so a later\n * `{{…}}` substitution pipes the full payload into the next tool —\n * even when the LLM only saw a head+tail-truncated preview in\n * `ToolMessage.content`. Outputs are stored without any annotation\n * (the `_ref` key or the `[ref: ...]` prefix seen by the LLM is\n * strictly a UX signal attached to `ToolMessage.content`). Keeping the\n * registry pristine means downstream bash/jq piping receives the\n * complete, verbatim output with no injected fields.\n */\n\nimport { ToolMessage } from '@langchain/core/messages';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport {\n calculateMaxTotalToolOutputSize,\n HARD_MAX_TOOL_RESULT_CHARS,\n HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE,\n} from '@/utils/truncation';\n\n/**\n * Non-global matcher for a single `{{tool<i>turn<n>}}` placeholder.\n * Exported for consumers that want to detect references (e.g., syntax\n * highlighting, docs). The stateful `g` variant lives inside the\n * registry so nobody trips on `lastIndex`.\n */\nexport const TOOL_OUTPUT_REF_PATTERN = /\\{\\{(tool\\d+turn\\d+)\\}\\}/;\n\n/** Object key used when a parsed-object output has `_ref` injected. */\nexport const TOOL_OUTPUT_REF_KEY = '_ref';\n\n/**\n * Object key used to carry unresolved reference warnings on a parsed-\n * object output. Using a dedicated field instead of a trailing text\n * line keeps the annotated `ToolMessage.content` parseable as JSON for\n * downstream consumers that rely on the object shape.\n */\nexport const TOOL_OUTPUT_UNRESOLVED_KEY = '_unresolved_refs';\n\n/** Single-line prefix prepended to non-object tool outputs so the LLM sees the reference key. */\nexport function buildReferencePrefix(key: string): string {\n return `[ref: ${key}]`;\n}\n\n/** Stable registry key for a tool output. */\nexport function buildReferenceKey(toolIndex: number, turn: number): string {\n return `tool${toolIndex}turn${turn}`;\n}\n\nexport type ToolOutputReferenceRegistryOptions = {\n /** Maximum characters stored per registered output. */\n maxOutputSize?: number;\n /** Maximum total characters retained across all registered outputs. */\n maxTotalSize?: number;\n /**\n * Upper bound on the number of concurrently-tracked runs. When\n * exceeded, the oldest run bucket is evicted (FIFO). Defaults to 32.\n */\n maxActiveRuns?: number;\n};\n\n/**\n * Result of resolving placeholders in tool args.\n */\nexport type ResolveResult<T> = {\n /** Arguments with placeholders replaced. Same shape as the input. */\n resolved: T;\n /** Reference keys that were referenced but had no stored value. */\n unresolved: string[];\n};\n\n/**\n * Read-only view over a frozen registry snapshot. Returned by\n * {@link ToolOutputReferenceRegistry.snapshot} for callers that need\n * to resolve placeholders against the registry state at a specific\n * point in time, ignoring any subsequent registrations.\n */\nexport interface ToolOutputResolveView {\n resolve<T>(args: T): ResolveResult<T>;\n}\n\n/**\n * Pre-resolved arg map keyed by `toolCallId`. Used by the mixed\n * direct+event dispatch path to feed event calls' resolved args\n * (captured pre-batch) into the dispatcher without re-resolving\n * against the now-stale live registry.\n */\nexport type PreResolvedArgsMap = Map<\n string,\n { resolved: Record<string, unknown>; unresolved: string[] }\n>;\n\n/**\n * Per-call sink for resolved args, keyed by `toolCallId`. Threaded\n * as a per-batch local map so concurrent `ToolNode.run()` calls do\n * not race on shared sink state.\n */\nexport type ResolvedArgsByCallId = Map<string, Record<string, unknown>>;\n\nconst EMPTY_ENTRIES: ReadonlyMap<string, string> = new Map<string, string>();\n\n/**\n * Per-run state bucket held inside the registry. Each distinct\n * `run_id` gets its own bucket so overlapping concurrent runs on a\n * shared registry cannot leak outputs, turn counters, or warn-memos\n * into one another.\n */\nclass RunStateBucket {\n entries: Map<string, string> = new Map();\n totalSize: number = 0;\n turnCounter: number = 0;\n warnedNonStringTools: Set<string> = new Set();\n}\n\n/**\n * Anonymous (`run_id` absent) bucket key. Anonymous batches are\n * treated as fresh runs on every invocation — see `nextTurn`.\n */\nconst ANON_RUN_KEY = '\\0anon';\n\n/**\n * Default upper bound on the number of concurrently-tracked runs per\n * registry. When exceeded, the oldest run's bucket (by insertion\n * order) is evicted. Keeps memory bounded when a ToolNode is reused\n * across many runs without explicit `releaseRun` calls.\n */\nconst DEFAULT_MAX_ACTIVE_RUNS = 32;\n\n/**\n * Ordered map of reference-key → stored output, partitioned by run so\n * concurrent / interleaved runs sharing one registry cannot leak\n * outputs between each other.\n *\n * Each public method takes a `runId` which selects the run's bucket.\n * Hosts typically get one registry per run via `Graph`, in which\n * case only a single bucket is ever populated; the partitioning\n * exists so the registry also behaves correctly when a single\n * instance is reused directly.\n */\nexport class ToolOutputReferenceRegistry {\n private runStates: Map<string, RunStateBucket> = new Map();\n private readonly maxOutputSize: number;\n private readonly maxTotalSize: number;\n private readonly maxActiveRuns: number;\n /**\n * Local stateful matcher used only by `replaceInString`. Kept\n * off-module so callers of the exported `TOOL_OUTPUT_REF_PATTERN`\n * never see a stale `lastIndex`.\n */\n private static readonly PLACEHOLDER_MATCHER = /\\{\\{(tool\\d+turn\\d+)\\}\\}/g;\n\n constructor(options: ToolOutputReferenceRegistryOptions = {}) {\n /**\n * Per-output default is the same ~400 KB budget as the standard\n * tool-result truncation (`HARD_MAX_TOOL_RESULT_CHARS`). This\n * keeps a single `{{…}}` substitution at a size that is safe to\n * pass through typical shell `ARG_MAX` limits and matches what\n * the LLM would otherwise have seen. Hosts that want larger per-\n * output payloads (API consumers, long JSON streams) can raise\n * the cap explicitly up to the 5 MB total budget.\n */\n const perOutput =\n options.maxOutputSize != null && options.maxOutputSize > 0\n ? options.maxOutputSize\n : HARD_MAX_TOOL_RESULT_CHARS;\n /**\n * Clamp a caller-supplied `maxTotalSize` to\n * `HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE` (5 MB) so the documented\n * absolute cap is enforced regardless of host config —\n * `calculateMaxTotalToolOutputSize` already applies the same\n * upper bound on its computed default, but the user-provided\n * branch was bypassing it.\n */\n const totalRaw =\n options.maxTotalSize != null && options.maxTotalSize > 0\n ? Math.min(options.maxTotalSize, HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE)\n : calculateMaxTotalToolOutputSize(perOutput);\n this.maxTotalSize = totalRaw;\n /**\n * The per-output cap can never exceed the per-run aggregate cap:\n * if a single entry were allowed to be larger than `maxTotalSize`,\n * the eviction loop would either blow the cap (to keep the entry)\n * or self-evict a just-stored value. Clamping here turns\n * `maxTotalSize` into a hard upper bound on *any* state the\n * registry retains per run.\n */\n this.maxOutputSize = Math.min(perOutput, totalRaw);\n this.maxActiveRuns =\n options.maxActiveRuns != null && options.maxActiveRuns > 0\n ? options.maxActiveRuns\n : DEFAULT_MAX_ACTIVE_RUNS;\n }\n\n private keyFor(runId: string | undefined): string {\n return runId ?? ANON_RUN_KEY;\n }\n\n private getOrCreate(runId: string | undefined): RunStateBucket {\n const key = this.keyFor(runId);\n let state = this.runStates.get(key);\n if (state == null) {\n state = new RunStateBucket();\n this.runStates.set(key, state);\n if (this.runStates.size > this.maxActiveRuns) {\n const oldest = this.runStates.keys().next().value;\n if (oldest != null && oldest !== key) {\n this.runStates.delete(oldest);\n }\n }\n }\n return state;\n }\n\n /** Registers (or replaces) the output stored under `key` for `runId`. */\n set(runId: string | undefined, key: string, value: string): void {\n const bucket = this.getOrCreate(runId);\n const clipped =\n value.length > this.maxOutputSize\n ? value.slice(0, this.maxOutputSize)\n : value;\n const existing = bucket.entries.get(key);\n if (existing != null) {\n bucket.totalSize -= existing.length;\n bucket.entries.delete(key);\n }\n bucket.entries.set(key, clipped);\n bucket.totalSize += clipped.length;\n this.evictWithinBucket(bucket);\n }\n\n /** Returns the stored value for `key` in `runId`'s bucket, or `undefined`. */\n get(runId: string | undefined, key: string): string | undefined {\n return this.runStates.get(this.keyFor(runId))?.entries.get(key);\n }\n\n /**\n * Returns `true` when `key` is currently stored in `runId`'s bucket.\n * Used by {@link annotateMessagesForLLM} to gate transient annotation\n * on whether the registry still owns the referenced output (a stale\n * `_refKey` from a prior run silently no-ops here).\n */\n has(runId: string | undefined, key: string): boolean {\n return this.runStates.get(this.keyFor(runId))?.entries.has(key) ?? false;\n }\n\n /** Total number of registered outputs across every run bucket. */\n get size(): number {\n let n = 0;\n for (const bucket of this.runStates.values()) {\n n += bucket.entries.size;\n }\n return n;\n }\n\n /** Maximum characters retained per output (post-clip). */\n get perOutputLimit(): number {\n return this.maxOutputSize;\n }\n\n /** Maximum total characters retained *per run*. */\n get totalLimit(): number {\n return this.maxTotalSize;\n }\n\n /** Drops every run's state. */\n clear(): void {\n this.runStates.clear();\n }\n\n /**\n * Explicitly release `runId`'s state. Safe to call when a run has\n * finished. Hosts sharing one registry across runs should call this\n * to reclaim memory deterministically; otherwise LRU eviction kicks\n * in when `maxActiveRuns` runs accumulate.\n */\n releaseRun(runId: string | undefined): void {\n this.runStates.delete(this.keyFor(runId));\n }\n\n /**\n * Claims the next batch turn synchronously from `runId`'s bucket.\n *\n * Must be called once at the start of each ToolNode batch before\n * any `await`, so concurrent invocations within the same run see\n * distinct turn values (reads are effectively atomic by JS's\n * single-threaded execution of the sync prefix).\n *\n * If `runId` is missing the anonymous bucket is dropped and a\n * fresh one created so each anonymous call behaves as its own run.\n */\n nextTurn(runId: string | undefined): number {\n if (runId == null) {\n this.runStates.delete(ANON_RUN_KEY);\n }\n const bucket = this.getOrCreate(runId);\n return bucket.turnCounter++;\n }\n\n /**\n * Records that `toolName` has been warned about in `runId` (returns\n * `true` on the first call per run, `false` after). Used by\n * ToolNode to emit one log line per offending tool per run when a\n * `ToolMessage.content` isn't a string.\n */\n claimWarnOnce(runId: string | undefined, toolName: string): boolean {\n const bucket = this.getOrCreate(runId);\n if (bucket.warnedNonStringTools.has(toolName)) {\n return false;\n }\n bucket.warnedNonStringTools.add(toolName);\n return true;\n }\n\n /**\n * Walks `args` and replaces every `{{tool<i>turn<n>}}` placeholder in\n * string values with the stored output *from `runId`'s bucket*. Non-\n * string values and object keys are left untouched. Unresolved\n * references are left in-place and reported so the caller can\n * surface them to the LLM. When no placeholder appears anywhere in\n * the serialized args, the original input is returned without\n * walking the tree.\n */\n resolve<T>(runId: string | undefined, args: T): ResolveResult<T> {\n if (!hasAnyPlaceholder(args)) {\n return { resolved: args, unresolved: [] };\n }\n const bucket = this.runStates.get(this.keyFor(runId));\n return this.resolveAgainst(bucket?.entries ?? EMPTY_ENTRIES, args);\n }\n\n /**\n * Captures a frozen snapshot of `runId`'s current entries and\n * returns a view that resolves placeholders against *only* that\n * snapshot. The snapshot is decoupled from the live registry, so\n * subsequent `set()` calls (for example, same-turn direct outputs\n * registering while an event branch is still in flight) are\n * invisible to the snapshot's `resolve`. Used by the mixed\n * direct+event dispatch path to preserve same-turn isolation when\n * a `PreToolUse` hook rewrites event args after directs have\n * completed.\n */\n snapshot(runId: string | undefined): ToolOutputResolveView {\n const bucket = this.runStates.get(this.keyFor(runId));\n const entries: ReadonlyMap<string, string> = bucket\n ? new Map(bucket.entries)\n : EMPTY_ENTRIES;\n return {\n resolve: <T>(args: T): ResolveResult<T> =>\n this.resolveAgainst(entries, args),\n };\n }\n\n private resolveAgainst<T>(\n entries: ReadonlyMap<string, string>,\n args: T\n ): ResolveResult<T> {\n if (!hasAnyPlaceholder(args)) {\n return { resolved: args, unresolved: [] };\n }\n const unresolved = new Set<string>();\n const resolved = this.transform(entries, args, unresolved) as T;\n return { resolved, unresolved: Array.from(unresolved) };\n }\n\n private transform(\n entries: ReadonlyMap<string, string>,\n value: unknown,\n unresolved: Set<string>\n ): unknown {\n if (typeof value === 'string') {\n return this.replaceInString(entries, value, unresolved);\n }\n if (Array.isArray(value)) {\n return value.map((item) => this.transform(entries, item, unresolved));\n }\n if (value !== null && typeof value === 'object') {\n const source = value as Record<string, unknown>;\n const next: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(source)) {\n next[key] = this.transform(entries, item, unresolved);\n }\n return next;\n }\n return value;\n }\n\n private replaceInString(\n entries: ReadonlyMap<string, string>,\n input: string,\n unresolved: Set<string>\n ): string {\n if (input.indexOf('{{tool') === -1) {\n return input;\n }\n return input.replace(\n ToolOutputReferenceRegistry.PLACEHOLDER_MATCHER,\n (match, key: string) => {\n const stored = entries.get(key);\n if (stored == null) {\n unresolved.add(key);\n return match;\n }\n return stored;\n }\n );\n }\n\n private evictWithinBucket(bucket: RunStateBucket): void {\n if (bucket.totalSize <= this.maxTotalSize) {\n return;\n }\n for (const key of bucket.entries.keys()) {\n if (bucket.totalSize <= this.maxTotalSize) {\n return;\n }\n const entry = bucket.entries.get(key);\n if (entry == null) {\n continue;\n }\n bucket.totalSize -= entry.length;\n bucket.entries.delete(key);\n }\n }\n}\n\n/**\n * Cheap pre-check: returns true if any string value in `args` contains\n * the `{{tool` substring. Lets `resolve()` skip the deep tree walk (and\n * its object allocations) for the common case of plain args.\n */\nfunction hasAnyPlaceholder(value: unknown): boolean {\n if (typeof value === 'string') {\n return value.indexOf('{{tool') !== -1;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n if (hasAnyPlaceholder(item)) {\n return true;\n }\n }\n return false;\n }\n if (value !== null && typeof value === 'object') {\n for (const item of Object.values(value as Record<string, unknown>)) {\n if (hasAnyPlaceholder(item)) {\n return true;\n }\n }\n return false;\n }\n return false;\n}\n\n/**\n * Annotates `content` with a reference key and/or unresolved-ref\n * warnings so the LLM sees both alongside the tool output.\n *\n * Behavior:\n * - If `content` parses as a plain (non-array, non-null) JSON object\n * and the object does not already have a conflicting `_ref` key,\n * the reference key and (when present) `_unresolved_refs` array\n * are injected as object fields, preserving JSON validity for\n * downstream consumers that parse the output.\n * - Otherwise (string output, JSON array/primitive, parse failure,\n * or `_ref` collision), a `[ref: <key>]\\n` prefix line is\n * prepended and unresolved refs are appended as a trailing\n * `[unresolved refs: …]` line.\n *\n * The annotated string is what the LLM sees as `ToolMessage.content`.\n * The *original* (un-annotated) value is what gets stored in the\n * registry, so downstream piping remains pristine.\n *\n * @param content Raw (post-truncation) tool output.\n * @param key Reference key for this output, or undefined when\n * there is nothing to register (errors etc.).\n * @param unresolved Reference keys that failed to resolve during\n * argument substitution. Surfaced so the LLM can\n * self-correct its next tool call.\n */\nexport function annotateToolOutputWithReference(\n content: string,\n key: string | undefined,\n unresolved: string[] = []\n): string {\n const hasRefKey = key != null;\n const hasUnresolved = unresolved.length > 0;\n if (!hasRefKey && !hasUnresolved) {\n return content;\n }\n const trimmed = content.trimStart();\n if (trimmed.startsWith('{')) {\n const annotated = tryInjectRefIntoJsonObject(content, key, unresolved);\n if (annotated != null) {\n return annotated;\n }\n }\n const prefix = hasRefKey ? `${buildReferencePrefix(key!)}\\n` : '';\n const trailer = hasUnresolved\n ? `\\n[unresolved refs: ${unresolved.join(', ')}]`\n : '';\n return `${prefix}${content}${trailer}`;\n}\n\nfunction tryInjectRefIntoJsonObject(\n content: string,\n key: string | undefined,\n unresolved: string[]\n): string | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch {\n return null;\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n const injectingRef = key != null;\n const injectingUnresolved = unresolved.length > 0;\n\n /**\n * Reject the JSON-injection path (fall back to prefix form) when\n * either of our keys collides with real payload data:\n * - `_ref` collision: existing value is non-null and differs from\n * the key we're about to inject.\n * - `_unresolved_refs` collision: existing value is non-null and\n * is not a deep-equal match for the array we'd inject.\n * This keeps us from silently overwriting legitimate tool output.\n */\n if (\n injectingRef &&\n TOOL_OUTPUT_REF_KEY in obj &&\n obj[TOOL_OUTPUT_REF_KEY] !== key &&\n obj[TOOL_OUTPUT_REF_KEY] != null\n ) {\n return null;\n }\n if (\n injectingUnresolved &&\n TOOL_OUTPUT_UNRESOLVED_KEY in obj &&\n obj[TOOL_OUTPUT_UNRESOLVED_KEY] != null &&\n !arraysShallowEqual(obj[TOOL_OUTPUT_UNRESOLVED_KEY], unresolved)\n ) {\n return null;\n }\n\n /**\n * Only strip the framework-owned key we're actually injecting —\n * leave everything else (including a pre-existing `_ref` on the\n * unresolved-only path, or a pre-existing `_unresolved_refs` on a\n * plain-annotation path) untouched so we annotate rather than\n * mutate downstream payload data. Our injected keys land first in\n * the serialized JSON so the LLM sees them before the body.\n */\n const omitKeys = new Set<string>();\n if (injectingRef) omitKeys.add(TOOL_OUTPUT_REF_KEY);\n if (injectingUnresolved) omitKeys.add(TOOL_OUTPUT_UNRESOLVED_KEY);\n const rest: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!omitKeys.has(k)) {\n rest[k] = v;\n }\n }\n const injected: Record<string, unknown> = {};\n if (injectingRef) {\n injected[TOOL_OUTPUT_REF_KEY] = key;\n }\n if (injectingUnresolved) {\n injected[TOOL_OUTPUT_UNRESOLVED_KEY] = unresolved;\n }\n Object.assign(injected, rest);\n\n const pretty = /^\\{\\s*\\n/.test(content);\n return pretty ? JSON.stringify(injected, null, 2) : JSON.stringify(injected);\n}\n\nfunction arraysShallowEqual(a: unknown, b: readonly string[]): boolean {\n if (!Array.isArray(a) || a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Lazy projection that, given a registry and a runId, returns a new\n * `messages` array where each `ToolMessage` carrying ref metadata is\n * projected into a transient copy with annotated content (when the ref\n * is live in the registry) and with the framework-owned `additional_\n * kwargs` keys (`_refKey`, `_refScope`, `_unresolvedRefs`) stripped\n * regardless of whether annotation applied. The original input array\n * and its messages are never mutated.\n *\n * Annotation is gated on registry presence: a stale `_refKey` from a\n * prior run (e.g. one that survived in persisted history) silently\n * no-ops on the *content* side. The strip-metadata side still runs so\n * stale framework keys never leak onto the wire under any custom or\n * future provider serializer that might transmit `additional_kwargs`.\n * `_unresolvedRefs` is always meaningful and is not gated.\n *\n * **Feature-disabled fast path:** when the host hasn't enabled the\n * tool-output-reference feature, the registry is `undefined` and this\n * function returns the input array reference-equal *without iterating\n * a single message*. The loop is exclusive to the feature-enabled\n * code path.\n */\nexport function annotateMessagesForLLM(\n messages: BaseMessage[],\n registry: ToolOutputReferenceRegistry | undefined,\n runId: string | undefined\n): BaseMessage[] {\n if (registry == null) return messages;\n\n /**\n * Lazy-allocate the output array so the common case (no ToolMessage\n * carries framework metadata) returns the input reference-equal with\n * zero allocations beyond the per-message predicate checks.\n */\n let out: BaseMessage[] | undefined;\n for (let i = 0; i < messages.length; i++) {\n const m = messages[i];\n if (m._getType() !== 'tool') continue;\n /**\n * `additional_kwargs` is untyped at the LangChain layer\n * (`Record<string, unknown>`), so persisted or client-supplied\n * ToolMessages can carry arbitrary shapes — including primitives\n * (a malformed serializer might write a string, or `null`).\n * Guard with a runtime object check before the `in` probes\n * because the `in` operator throws `TypeError` on primitives.\n * A single malformed message must never crash the provider call\n * path; skip its annotation/strip and continue.\n */\n const rawMeta = m.additional_kwargs as unknown;\n if (rawMeta == null || typeof rawMeta !== 'object') continue;\n const meta = rawMeta as Record<string, unknown>;\n const hasRefKey = '_refKey' in meta;\n const hasRefScope = '_refScope' in meta;\n const hasUnresolvedField = '_unresolvedRefs' in meta;\n if (!hasRefKey && !hasRefScope && !hasUnresolvedField) continue;\n\n const refKey = readRefKey(meta);\n const unresolved = readUnresolvedRefs(meta);\n\n /**\n * Prefer the message-stamped `_refScope` for the registry lookup.\n * For named runs it equals the current `runId`; for anonymous\n * invocations it carries the per-batch synthetic scope minted by\n * ToolNode (`\\0anon-<n>`), which `runId` from config cannot\n * recover. Falling back to `runId` keeps backward compatibility\n * with messages stamped before this field existed.\n */\n const lookupScope = readRefScope(meta) ?? runId;\n const liveRef =\n refKey != null && registry.has(lookupScope, refKey) ? refKey : undefined;\n const annotates = liveRef != null || unresolved.length > 0;\n\n const tm = m as ToolMessage;\n let nextContent: ToolMessage['content'] = tm.content;\n\n if (annotates && typeof tm.content === 'string') {\n nextContent = annotateToolOutputWithReference(\n tm.content,\n liveRef,\n unresolved\n );\n } else if (\n annotates &&\n Array.isArray(tm.content) &&\n unresolved.length > 0\n ) {\n const warningBlock = {\n type: 'text' as const,\n text: `[unresolved refs: ${unresolved.join(', ')}]`,\n };\n /**\n * `as unknown as ToolMessage['content']` is unavoidable here:\n * LangChain's content union (`MessageContentComplex[] |\n * DataContentBlock[] | string`) does not accept a freshly built\n * mixed array literal even though the structural shape is valid\n * at runtime. The double-cast is structurally safe — we\n * preserve every block from `tm.content` and prepend a single\n * `{ type: 'text', text }` block that all providers accept.\n */\n nextContent = [\n warningBlock,\n ...tm.content,\n ] as unknown as ToolMessage['content'];\n }\n\n /**\n * Project unconditionally: even when no annotation applies (stale\n * `_refKey` or non-annotatable content), `cloneToolMessageWithContent`\n * runs `stripFrameworkRefMetadata` on `additional_kwargs` so the\n * framework-owned keys never reach the wire.\n */\n out ??= messages.slice();\n out[i] = cloneToolMessageWithContent(tm, nextContent);\n }\n\n return out ?? messages;\n}\n\n/**\n * Reads `_refKey` defensively from untyped `additional_kwargs`. Returns\n * undefined for non-string values so a malformed field cannot poison\n * the registry lookup or downstream string operations.\n */\nfunction readRefKey(\n meta: Record<string, unknown> | undefined\n): string | undefined {\n const v = meta?._refKey;\n return typeof v === 'string' ? v : undefined;\n}\n\n/**\n * Reads `_refScope` defensively from untyped `additional_kwargs`.\n * Mirrors {@link readRefKey} — non-string scopes are dropped (the\n * caller falls back to the run-derived scope) rather than passed into\n * the registry as a malformed key.\n */\nfunction readRefScope(\n meta: Record<string, unknown> | undefined\n): string | undefined {\n const v = meta?._refScope;\n return typeof v === 'string' ? v : undefined;\n}\n\n/**\n * Reads `_unresolvedRefs` defensively from untyped `additional_kwargs`.\n * Returns an empty array for any non-array value, and filters out\n * non-string entries from a real array. Without this guard, a hydrated\n * ToolMessage carrying e.g. `_unresolvedRefs: 'tool0turn0'` would crash\n * `attemptInvoke` on the eventual `.length` / `.join(...)` call.\n */\nfunction readUnresolvedRefs(\n meta: Record<string, unknown> | undefined\n): string[] {\n const v = meta?._unresolvedRefs;\n if (!Array.isArray(v)) return [];\n const out: string[] = [];\n for (const item of v) {\n if (typeof item === 'string') out.push(item);\n }\n return out;\n}\n\n/**\n * Builds a fresh `ToolMessage` that mirrors `tm`'s identity fields with\n * the supplied `content`. Every `ToolMessage` field but `content` is\n * carried over so the projection is structurally identical to the\n * original from a LangChain serializer's perspective.\n *\n * `additional_kwargs` is rebuilt with the framework-owned ref keys\n * stripped. Defensive: LangChain's standard provider serializers do not\n * transmit `additional_kwargs` to provider HTTP APIs, but a custom\n * adapter or future LangChain change could. Stripping keeps the\n * implementation correct under any serializer behavior at the cost of a\n * shallow object spread per annotated message.\n */\nfunction cloneToolMessageWithContent(\n tm: ToolMessage,\n content: ToolMessage['content']\n): ToolMessage {\n return new ToolMessage({\n id: tm.id,\n name: tm.name,\n status: tm.status,\n artifact: tm.artifact,\n tool_call_id: tm.tool_call_id,\n response_metadata: tm.response_metadata,\n additional_kwargs: stripFrameworkRefMetadata(tm.additional_kwargs),\n content,\n });\n}\n\n/**\n * Returns a copy of `kwargs` with `_refKey`, `_refScope`, and\n * `_unresolvedRefs` removed. Returns the input reference-equal when\n * none of those keys are present so the no-strip path stays cheap;\n * returns `undefined` when stripping leaves the object empty so the\n * caller can drop the field entirely.\n */\nfunction stripFrameworkRefMetadata(\n kwargs: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (kwargs == null) return undefined;\n if (\n !('_refKey' in kwargs) &&\n !('_refScope' in kwargs) &&\n !('_unresolvedRefs' in kwargs)\n ) {\n return kwargs;\n }\n const { _refKey, _refScope, _unresolvedRefs, ...rest } = kwargs as Record<\n string,\n unknown\n > & {\n _refKey?: unknown;\n _refScope?: unknown;\n _unresolvedRefs?: unknown;\n };\n void _refKey;\n void _refScope;\n void _unresolvedRefs;\n return Object.keys(rest).length === 0 ? undefined : rest;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolOutputReferences.mjs","names":[],"sources":["../../../src/tools/toolOutputReferences.ts"],"sourcesContent":["/**\n * Tool output reference registry.\n *\n * When enabled via `RunConfig.toolOutputReferences.enabled`, ToolNode\n * stores each successful tool output under a stable key\n * (`tool<idx>turn<turn>`) where `idx` is the tool's position within a\n * ToolNode batch and `turn` is the batch index within the run\n * (incremented once per ToolNode invocation).\n *\n * Subsequent tool calls can pipe a previous output into their args by\n * embedding `{{tool<idx>turn<turn>}}` inside any string argument;\n * {@link ToolOutputReferenceRegistry.resolve} walks the args and\n * substitutes the placeholders immediately before invocation.\n *\n * The registry stores the *raw, untruncated* tool output so a later\n * `{{…}}` substitution pipes the full payload into the next tool —\n * even when the LLM only saw a head+tail-truncated preview in\n * `ToolMessage.content`. Outputs are stored without any annotation\n * (the `_ref` key or the `[ref: ...]` prefix seen by the LLM is\n * strictly a UX signal attached to `ToolMessage.content`). Keeping the\n * registry pristine means downstream bash/jq piping receives the\n * complete, verbatim output with no injected fields.\n */\n\nimport { ToolMessage } from '@langchain/core/messages';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport {\n calculateMaxTotalToolOutputSize,\n HARD_MAX_TOOL_RESULT_CHARS,\n HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE,\n} from '@/utils/truncation';\n\n/**\n * Non-global matcher for a single `{{tool<i>turn<n>}}` placeholder.\n * Exported for consumers that want to detect references (e.g., syntax\n * highlighting, docs). The stateful `g` variant lives inside the\n * registry so nobody trips on `lastIndex`.\n */\nexport const TOOL_OUTPUT_REF_PATTERN = /\\{\\{(tool\\d+turn\\d+)\\}\\}/;\n\n/** Object key used when a parsed-object output has `_ref` injected. */\nexport const TOOL_OUTPUT_REF_KEY = '_ref';\n\n/**\n * Object key used to carry unresolved reference warnings on a parsed-\n * object output. Using a dedicated field instead of a trailing text\n * line keeps the annotated `ToolMessage.content` parseable as JSON for\n * downstream consumers that rely on the object shape.\n */\nexport const TOOL_OUTPUT_UNRESOLVED_KEY = '_unresolved_refs';\n\n/** Single-line prefix prepended to non-object tool outputs so the LLM sees the reference key. */\nexport function buildReferencePrefix(key: string): string {\n return `[ref: ${key}]`;\n}\n\n/** Stable registry key for a tool output. */\nexport function buildReferenceKey(toolIndex: number, turn: number): string {\n return `tool${toolIndex}turn${turn}`;\n}\n\nexport type ToolOutputReferenceRegistryOptions = {\n /** Maximum characters stored per registered output. */\n maxOutputSize?: number;\n /** Maximum total characters retained across all registered outputs. */\n maxTotalSize?: number;\n /**\n * Upper bound on the number of concurrently-tracked runs. When\n * exceeded, the oldest run bucket is evicted (FIFO). Defaults to 32.\n */\n maxActiveRuns?: number;\n};\n\n/**\n * Result of resolving placeholders in tool args.\n */\nexport type ResolveResult<T> = {\n /** Arguments with placeholders replaced. Same shape as the input. */\n resolved: T;\n /** Reference keys that were referenced but had no stored value. */\n unresolved: string[];\n};\n\n/**\n * Read-only view over a frozen registry snapshot. Returned by\n * {@link ToolOutputReferenceRegistry.snapshot} for callers that need\n * to resolve placeholders against the registry state at a specific\n * point in time, ignoring any subsequent registrations.\n */\nexport interface ToolOutputResolveView {\n resolve<T>(args: T): ResolveResult<T>;\n}\n\n/**\n * Pre-resolved arg map keyed by `toolCallId`. Used by the mixed\n * direct+event dispatch path to feed event calls' resolved args\n * (captured pre-batch) into the dispatcher without re-resolving\n * against the now-stale live registry.\n */\nexport type PreResolvedArgsMap = Map<\n string,\n { resolved: Record<string, unknown>; unresolved: string[] }\n>;\n\n/**\n * Per-call sink for resolved args, keyed by `toolCallId`. Threaded\n * as a per-batch local map so concurrent `ToolNode.run()` calls do\n * not race on shared sink state.\n */\nexport type ResolvedArgsByCallId = Map<string, Record<string, unknown>>;\n\nconst EMPTY_ENTRIES: ReadonlyMap<string, string> = new Map<string, string>();\n\n/**\n * Per-run state bucket held inside the registry. Each distinct\n * `run_id` gets its own bucket so overlapping concurrent runs on a\n * shared registry cannot leak outputs, turn counters, or warn-memos\n * into one another.\n */\nclass RunStateBucket {\n entries: Map<string, string> = new Map();\n totalSize: number = 0;\n turnCounter: number = 0;\n warnedNonStringTools: Set<string> = new Set();\n}\n\n/**\n * Anonymous (`run_id` absent) bucket key. Anonymous batches are\n * treated as fresh runs on every invocation — see `nextTurn`.\n */\nconst ANON_RUN_KEY = '\\0anon';\n\n/**\n * Default upper bound on the number of concurrently-tracked runs per\n * registry. When exceeded, the oldest run's bucket (by insertion\n * order) is evicted. Keeps memory bounded when a ToolNode is reused\n * across many runs without explicit `releaseRun` calls.\n */\nconst DEFAULT_MAX_ACTIVE_RUNS = 32;\n\n/**\n * Ordered map of reference-key → stored output, partitioned by run so\n * concurrent / interleaved runs sharing one registry cannot leak\n * outputs between each other.\n *\n * Each public method takes a `runId` which selects the run's bucket.\n * Hosts typically get one registry per run via `Graph`, in which\n * case only a single bucket is ever populated; the partitioning\n * exists so the registry also behaves correctly when a single\n * instance is reused directly.\n */\nexport class ToolOutputReferenceRegistry {\n private runStates: Map<string, RunStateBucket> = new Map();\n private readonly maxOutputSize: number;\n private readonly maxTotalSize: number;\n private readonly maxActiveRuns: number;\n /**\n * Local stateful matcher used only by `replaceInString`. Kept\n * off-module so callers of the exported `TOOL_OUTPUT_REF_PATTERN`\n * never see a stale `lastIndex`.\n */\n private static readonly PLACEHOLDER_MATCHER = /\\{\\{(tool\\d+turn\\d+)\\}\\}/g;\n\n constructor(options: ToolOutputReferenceRegistryOptions = {}) {\n /**\n * Per-output default is the same ~400 KB budget as the standard\n * tool-result truncation (`HARD_MAX_TOOL_RESULT_CHARS`). This\n * keeps a single `{{…}}` substitution at a size that is safe to\n * pass through typical shell `ARG_MAX` limits and matches what\n * the LLM would otherwise have seen. Hosts that want larger per-\n * output payloads (API consumers, long JSON streams) can raise\n * the cap explicitly up to the 5 MB total budget.\n */\n const perOutput =\n options.maxOutputSize != null && options.maxOutputSize > 0\n ? options.maxOutputSize\n : HARD_MAX_TOOL_RESULT_CHARS;\n /**\n * Clamp a caller-supplied `maxTotalSize` to\n * `HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE` (5 MB) so the documented\n * absolute cap is enforced regardless of host config —\n * `calculateMaxTotalToolOutputSize` already applies the same\n * upper bound on its computed default, but the user-provided\n * branch was bypassing it.\n */\n const totalRaw =\n options.maxTotalSize != null && options.maxTotalSize > 0\n ? Math.min(options.maxTotalSize, HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE)\n : calculateMaxTotalToolOutputSize(perOutput);\n this.maxTotalSize = totalRaw;\n /**\n * The per-output cap can never exceed the per-run aggregate cap:\n * if a single entry were allowed to be larger than `maxTotalSize`,\n * the eviction loop would either blow the cap (to keep the entry)\n * or self-evict a just-stored value. Clamping here turns\n * `maxTotalSize` into a hard upper bound on *any* state the\n * registry retains per run.\n */\n this.maxOutputSize = Math.min(perOutput, totalRaw);\n this.maxActiveRuns =\n options.maxActiveRuns != null && options.maxActiveRuns > 0\n ? options.maxActiveRuns\n : DEFAULT_MAX_ACTIVE_RUNS;\n }\n\n private keyFor(runId: string | undefined): string {\n return runId ?? ANON_RUN_KEY;\n }\n\n private getOrCreate(runId: string | undefined): RunStateBucket {\n const key = this.keyFor(runId);\n let state = this.runStates.get(key);\n if (state == null) {\n state = new RunStateBucket();\n this.runStates.set(key, state);\n if (this.runStates.size > this.maxActiveRuns) {\n const oldest = this.runStates.keys().next().value;\n if (oldest != null && oldest !== key) {\n this.runStates.delete(oldest);\n }\n }\n }\n return state;\n }\n\n /** Registers (or replaces) the output stored under `key` for `runId`. */\n set(runId: string | undefined, key: string, value: string): void {\n const bucket = this.getOrCreate(runId);\n const clipped =\n value.length > this.maxOutputSize\n ? value.slice(0, this.maxOutputSize)\n : value;\n const existing = bucket.entries.get(key);\n if (existing != null) {\n bucket.totalSize -= existing.length;\n bucket.entries.delete(key);\n }\n bucket.entries.set(key, clipped);\n bucket.totalSize += clipped.length;\n this.evictWithinBucket(bucket);\n }\n\n /** Returns the stored value for `key` in `runId`'s bucket, or `undefined`. */\n get(runId: string | undefined, key: string): string | undefined {\n return this.runStates.get(this.keyFor(runId))?.entries.get(key);\n }\n\n /**\n * Returns `true` when `key` is currently stored in `runId`'s bucket.\n * Used by {@link annotateMessagesForLLM} to gate transient annotation\n * on whether the registry still owns the referenced output (a stale\n * `_refKey` from a prior run silently no-ops here).\n */\n has(runId: string | undefined, key: string): boolean {\n return this.runStates.get(this.keyFor(runId))?.entries.has(key) ?? false;\n }\n\n /** Total number of registered outputs across every run bucket. */\n get size(): number {\n let n = 0;\n for (const bucket of this.runStates.values()) {\n n += bucket.entries.size;\n }\n return n;\n }\n\n /** Maximum characters retained per output (post-clip). */\n get perOutputLimit(): number {\n return this.maxOutputSize;\n }\n\n /** Maximum total characters retained *per run*. */\n get totalLimit(): number {\n return this.maxTotalSize;\n }\n\n /** Drops every run's state. */\n clear(): void {\n this.runStates.clear();\n }\n\n /**\n * Explicitly release `runId`'s state. Safe to call when a run has\n * finished. Hosts sharing one registry across runs should call this\n * to reclaim memory deterministically; otherwise LRU eviction kicks\n * in when `maxActiveRuns` runs accumulate.\n */\n releaseRun(runId: string | undefined): void {\n this.runStates.delete(this.keyFor(runId));\n }\n\n /**\n * Claims the next batch turn synchronously from `runId`'s bucket.\n *\n * Must be called once at the start of each ToolNode batch before\n * any `await`, so concurrent invocations within the same run see\n * distinct turn values (reads are effectively atomic by JS's\n * single-threaded execution of the sync prefix).\n *\n * If `runId` is missing the anonymous bucket is dropped and a\n * fresh one created so each anonymous call behaves as its own run.\n */\n nextTurn(runId: string | undefined): number {\n if (runId == null) {\n this.runStates.delete(ANON_RUN_KEY);\n }\n const bucket = this.getOrCreate(runId);\n return bucket.turnCounter++;\n }\n\n /**\n * Records that `toolName` has been warned about in `runId` (returns\n * `true` on the first call per run, `false` after). Used by\n * ToolNode to emit one log line per offending tool per run when a\n * `ToolMessage.content` isn't a string.\n */\n claimWarnOnce(runId: string | undefined, toolName: string): boolean {\n const bucket = this.getOrCreate(runId);\n if (bucket.warnedNonStringTools.has(toolName)) {\n return false;\n }\n bucket.warnedNonStringTools.add(toolName);\n return true;\n }\n\n /**\n * Walks `args` and replaces every `{{tool<i>turn<n>}}` placeholder in\n * string values with the stored output *from `runId`'s bucket*. Non-\n * string values and object keys are left untouched. Unresolved\n * references are left in-place and reported so the caller can\n * surface them to the LLM. When no placeholder appears anywhere in\n * the serialized args, the original input is returned without\n * walking the tree.\n */\n resolve<T>(runId: string | undefined, args: T): ResolveResult<T> {\n if (!hasAnyPlaceholder(args)) {\n return { resolved: args, unresolved: [] };\n }\n const bucket = this.runStates.get(this.keyFor(runId));\n return this.resolveAgainst(bucket?.entries ?? EMPTY_ENTRIES, args);\n }\n\n /**\n * Captures a frozen snapshot of `runId`'s current entries and\n * returns a view that resolves placeholders against *only* that\n * snapshot. The snapshot is decoupled from the live registry, so\n * subsequent `set()` calls (for example, same-turn direct outputs\n * registering while an event branch is still in flight) are\n * invisible to the snapshot's `resolve`. Used by the mixed\n * direct+event dispatch path to preserve same-turn isolation when\n * a `PreToolUse` hook rewrites event args after directs have\n * completed.\n */\n snapshot(runId: string | undefined): ToolOutputResolveView {\n const bucket = this.runStates.get(this.keyFor(runId));\n const entries: ReadonlyMap<string, string> = bucket\n ? new Map(bucket.entries)\n : EMPTY_ENTRIES;\n return {\n resolve: <T>(args: T): ResolveResult<T> =>\n this.resolveAgainst(entries, args),\n };\n }\n\n private resolveAgainst<T>(\n entries: ReadonlyMap<string, string>,\n args: T\n ): ResolveResult<T> {\n if (!hasAnyPlaceholder(args)) {\n return { resolved: args, unresolved: [] };\n }\n const unresolved = new Set<string>();\n const resolved = this.transform(entries, args, unresolved) as T;\n return { resolved, unresolved: Array.from(unresolved) };\n }\n\n private transform(\n entries: ReadonlyMap<string, string>,\n value: unknown,\n unresolved: Set<string>\n ): unknown {\n if (typeof value === 'string') {\n return this.replaceInString(entries, value, unresolved);\n }\n if (Array.isArray(value)) {\n return value.map((item) => this.transform(entries, item, unresolved));\n }\n if (value !== null && typeof value === 'object') {\n const source = value as Record<string, unknown>;\n const next: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(source)) {\n next[key] = this.transform(entries, item, unresolved);\n }\n return next;\n }\n return value;\n }\n\n private replaceInString(\n entries: ReadonlyMap<string, string>,\n input: string,\n unresolved: Set<string>\n ): string {\n if (input.indexOf('{{tool') === -1) {\n return input;\n }\n return input.replace(\n ToolOutputReferenceRegistry.PLACEHOLDER_MATCHER,\n (match, key: string) => {\n const stored = entries.get(key);\n if (stored == null) {\n unresolved.add(key);\n return match;\n }\n return stored;\n }\n );\n }\n\n private evictWithinBucket(bucket: RunStateBucket): void {\n if (bucket.totalSize <= this.maxTotalSize) {\n return;\n }\n for (const key of bucket.entries.keys()) {\n if (bucket.totalSize <= this.maxTotalSize) {\n return;\n }\n const entry = bucket.entries.get(key);\n if (entry == null) {\n continue;\n }\n bucket.totalSize -= entry.length;\n bucket.entries.delete(key);\n }\n }\n}\n\n/**\n * Cheap pre-check: returns true if any string value in `args` contains\n * the `{{tool` substring. Lets `resolve()` skip the deep tree walk (and\n * its object allocations) for the common case of plain args.\n */\nfunction hasAnyPlaceholder(value: unknown): boolean {\n if (typeof value === 'string') {\n return value.indexOf('{{tool') !== -1;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n if (hasAnyPlaceholder(item)) {\n return true;\n }\n }\n return false;\n }\n if (value !== null && typeof value === 'object') {\n for (const item of Object.values(value as Record<string, unknown>)) {\n if (hasAnyPlaceholder(item)) {\n return true;\n }\n }\n return false;\n }\n return false;\n}\n\n/**\n * Annotates `content` with a reference key and/or unresolved-ref\n * warnings so the LLM sees both alongside the tool output.\n *\n * Behavior:\n * - If `content` parses as a plain (non-array, non-null) JSON object\n * and the object does not already have a conflicting `_ref` key,\n * the reference key and (when present) `_unresolved_refs` array\n * are injected as object fields, preserving JSON validity for\n * downstream consumers that parse the output.\n * - Otherwise (string output, JSON array/primitive, parse failure,\n * or `_ref` collision), a `[ref: <key>]\\n` prefix line is\n * prepended and unresolved refs are appended as a trailing\n * `[unresolved refs: …]` line.\n *\n * The annotated string is what the LLM sees as `ToolMessage.content`.\n * The *original* (un-annotated) value is what gets stored in the\n * registry, so downstream piping remains pristine.\n *\n * @param content Raw (post-truncation) tool output.\n * @param key Reference key for this output, or undefined when\n * there is nothing to register (errors etc.).\n * @param unresolved Reference keys that failed to resolve during\n * argument substitution. Surfaced so the LLM can\n * self-correct its next tool call.\n */\nexport function annotateToolOutputWithReference(\n content: string,\n key: string | undefined,\n unresolved: string[] = []\n): string {\n const hasRefKey = key != null;\n const hasUnresolved = unresolved.length > 0;\n if (!hasRefKey && !hasUnresolved) {\n return content;\n }\n const trimmed = content.trimStart();\n if (trimmed.startsWith('{')) {\n const annotated = tryInjectRefIntoJsonObject(content, key, unresolved);\n if (annotated != null) {\n return annotated;\n }\n }\n const prefix = hasRefKey ? `${buildReferencePrefix(key!)}\\n` : '';\n const trailer = hasUnresolved\n ? `\\n[unresolved refs: ${unresolved.join(', ')}]`\n : '';\n return `${prefix}${content}${trailer}`;\n}\n\nfunction tryInjectRefIntoJsonObject(\n content: string,\n key: string | undefined,\n unresolved: string[]\n): string | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch {\n return null;\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n const injectingRef = key != null;\n const injectingUnresolved = unresolved.length > 0;\n\n /**\n * Reject the JSON-injection path (fall back to prefix form) when\n * either of our keys collides with real payload data:\n * - `_ref` collision: existing value is non-null and differs from\n * the key we're about to inject.\n * - `_unresolved_refs` collision: existing value is non-null and\n * is not a deep-equal match for the array we'd inject.\n * This keeps us from silently overwriting legitimate tool output.\n */\n if (\n injectingRef &&\n TOOL_OUTPUT_REF_KEY in obj &&\n obj[TOOL_OUTPUT_REF_KEY] !== key &&\n obj[TOOL_OUTPUT_REF_KEY] != null\n ) {\n return null;\n }\n if (\n injectingUnresolved &&\n TOOL_OUTPUT_UNRESOLVED_KEY in obj &&\n obj[TOOL_OUTPUT_UNRESOLVED_KEY] != null &&\n !arraysShallowEqual(obj[TOOL_OUTPUT_UNRESOLVED_KEY], unresolved)\n ) {\n return null;\n }\n\n /**\n * Only strip the framework-owned key we're actually injecting —\n * leave everything else (including a pre-existing `_ref` on the\n * unresolved-only path, or a pre-existing `_unresolved_refs` on a\n * plain-annotation path) untouched so we annotate rather than\n * mutate downstream payload data. Our injected keys land first in\n * the serialized JSON so the LLM sees them before the body.\n */\n const omitKeys = new Set<string>();\n if (injectingRef) omitKeys.add(TOOL_OUTPUT_REF_KEY);\n if (injectingUnresolved) omitKeys.add(TOOL_OUTPUT_UNRESOLVED_KEY);\n const rest: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!omitKeys.has(k)) {\n rest[k] = v;\n }\n }\n const injected: Record<string, unknown> = {};\n if (injectingRef) {\n injected[TOOL_OUTPUT_REF_KEY] = key;\n }\n if (injectingUnresolved) {\n injected[TOOL_OUTPUT_UNRESOLVED_KEY] = unresolved;\n }\n Object.assign(injected, rest);\n\n const pretty = /^\\{\\s*\\n/.test(content);\n return pretty ? JSON.stringify(injected, null, 2) : JSON.stringify(injected);\n}\n\nfunction arraysShallowEqual(a: unknown, b: readonly string[]): boolean {\n if (!Array.isArray(a) || a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Lazy projection that, given a registry and a runId, returns a new\n * `messages` array where each `ToolMessage` carrying ref metadata is\n * projected into a transient copy with annotated content (when the ref\n * is live in the registry) and with the framework-owned `additional_\n * kwargs` keys (`_refKey`, `_refScope`, `_unresolvedRefs`) stripped\n * regardless of whether annotation applied. The original input array\n * and its messages are never mutated.\n *\n * Annotation is gated on registry presence: a stale `_refKey` from a\n * prior run (e.g. one that survived in persisted history) silently\n * no-ops on the *content* side. The strip-metadata side still runs so\n * stale framework keys never leak onto the wire under any custom or\n * future provider serializer that might transmit `additional_kwargs`.\n * `_unresolvedRefs` is always meaningful and is not gated.\n *\n * **Feature-disabled fast path:** when the host hasn't enabled the\n * tool-output-reference feature, the registry is `undefined` and this\n * function returns the input array reference-equal *without iterating\n * a single message*. The loop is exclusive to the feature-enabled\n * code path.\n */\nexport function annotateMessagesForLLM(\n messages: BaseMessage[],\n registry: ToolOutputReferenceRegistry | undefined,\n runId: string | undefined\n): BaseMessage[] {\n if (registry == null) return messages;\n\n /**\n * Lazy-allocate the output array so the common case (no ToolMessage\n * carries framework metadata) returns the input reference-equal with\n * zero allocations beyond the per-message predicate checks.\n */\n let out: BaseMessage[] | undefined;\n for (let i = 0; i < messages.length; i++) {\n const m = messages[i];\n if (m._getType() !== 'tool') continue;\n /**\n * `additional_kwargs` is untyped at the LangChain layer\n * (`Record<string, unknown>`), so persisted or client-supplied\n * ToolMessages can carry arbitrary shapes — including primitives\n * (a malformed serializer might write a string, or `null`).\n * Guard with a runtime object check before the `in` probes\n * because the `in` operator throws `TypeError` on primitives.\n * A single malformed message must never crash the provider call\n * path; skip its annotation/strip and continue.\n */\n const rawMeta = m.additional_kwargs as unknown;\n if (rawMeta == null || typeof rawMeta !== 'object') continue;\n const meta = rawMeta as Record<string, unknown>;\n const hasRefKey = '_refKey' in meta;\n const hasRefScope = '_refScope' in meta;\n const hasUnresolvedField = '_unresolvedRefs' in meta;\n if (!hasRefKey && !hasRefScope && !hasUnresolvedField) continue;\n\n const refKey = readRefKey(meta);\n const unresolved = readUnresolvedRefs(meta);\n\n /**\n * Prefer the message-stamped `_refScope` for the registry lookup.\n * For named runs it equals the current `runId`; for anonymous\n * invocations it carries the per-batch synthetic scope minted by\n * ToolNode (`\\0anon-<n>`), which `runId` from config cannot\n * recover. Falling back to `runId` keeps backward compatibility\n * with messages stamped before this field existed.\n */\n const lookupScope = readRefScope(meta) ?? runId;\n const liveRef =\n refKey != null && registry.has(lookupScope, refKey) ? refKey : undefined;\n const annotates = liveRef != null || unresolved.length > 0;\n\n const tm = m as ToolMessage;\n let nextContent: ToolMessage['content'] = tm.content;\n\n if (annotates && typeof tm.content === 'string') {\n nextContent = annotateToolOutputWithReference(\n tm.content,\n liveRef,\n unresolved\n );\n } else if (annotates && Array.isArray(tm.content)) {\n /**\n * Array tool content. The string annotator can't run — this notably\n * includes a tail tool result that prompt caching rewrote from a string\n * into a text-block array to host its `cache_control` / `cachePoint`\n * marker (the `_refKey` survives on `additional_kwargs`). Project the\n * same markers the string path would, as leading text blocks: the live\n * `[ref: …]` prefix and/or the unresolved-refs warning. Without this the\n * common tool-result tail loses its reference marker once cached.\n *\n * `as unknown as ToolMessage['content']` is unavoidable: LangChain's\n * content union does not accept a freshly built mixed array literal even\n * though the structural shape is valid at runtime. The double-cast is\n * structurally safe — every original block is preserved and only\n * `{ type: 'text', text }` blocks (which all providers accept) are\n * prepended.\n */\n const prefixBlocks: Array<{ type: 'text'; text: string }> = [];\n if (liveRef != null) {\n prefixBlocks.push({\n type: 'text',\n text: buildReferencePrefix(liveRef),\n });\n }\n if (unresolved.length > 0) {\n prefixBlocks.push({\n type: 'text',\n text: `[unresolved refs: ${unresolved.join(', ')}]`,\n });\n }\n if (prefixBlocks.length > 0) {\n nextContent = [\n ...prefixBlocks,\n ...tm.content,\n ] as unknown as ToolMessage['content'];\n }\n }\n\n /**\n * Project unconditionally: even when no annotation applies (stale\n * `_refKey` or non-annotatable content), `cloneToolMessageWithContent`\n * runs `stripFrameworkRefMetadata` on `additional_kwargs` so the\n * framework-owned keys never reach the wire.\n */\n out ??= messages.slice();\n out[i] = cloneToolMessageWithContent(tm, nextContent);\n }\n\n return out ?? messages;\n}\n\n/**\n * Reads `_refKey` defensively from untyped `additional_kwargs`. Returns\n * undefined for non-string values so a malformed field cannot poison\n * the registry lookup or downstream string operations.\n */\nfunction readRefKey(\n meta: Record<string, unknown> | undefined\n): string | undefined {\n const v = meta?._refKey;\n return typeof v === 'string' ? v : undefined;\n}\n\n/**\n * Reads `_refScope` defensively from untyped `additional_kwargs`.\n * Mirrors {@link readRefKey} — non-string scopes are dropped (the\n * caller falls back to the run-derived scope) rather than passed into\n * the registry as a malformed key.\n */\nfunction readRefScope(\n meta: Record<string, unknown> | undefined\n): string | undefined {\n const v = meta?._refScope;\n return typeof v === 'string' ? v : undefined;\n}\n\n/**\n * Reads `_unresolvedRefs` defensively from untyped `additional_kwargs`.\n * Returns an empty array for any non-array value, and filters out\n * non-string entries from a real array. Without this guard, a hydrated\n * ToolMessage carrying e.g. `_unresolvedRefs: 'tool0turn0'` would crash\n * `attemptInvoke` on the eventual `.length` / `.join(...)` call.\n */\nfunction readUnresolvedRefs(\n meta: Record<string, unknown> | undefined\n): string[] {\n const v = meta?._unresolvedRefs;\n if (!Array.isArray(v)) return [];\n const out: string[] = [];\n for (const item of v) {\n if (typeof item === 'string') out.push(item);\n }\n return out;\n}\n\n/**\n * Builds a fresh `ToolMessage` that mirrors `tm`'s identity fields with\n * the supplied `content`. Every `ToolMessage` field but `content` is\n * carried over so the projection is structurally identical to the\n * original from a LangChain serializer's perspective.\n *\n * `additional_kwargs` is rebuilt with the framework-owned ref keys\n * stripped. Defensive: LangChain's standard provider serializers do not\n * transmit `additional_kwargs` to provider HTTP APIs, but a custom\n * adapter or future LangChain change could. Stripping keeps the\n * implementation correct under any serializer behavior at the cost of a\n * shallow object spread per annotated message.\n */\nfunction cloneToolMessageWithContent(\n tm: ToolMessage,\n content: ToolMessage['content']\n): ToolMessage {\n return new ToolMessage({\n id: tm.id,\n name: tm.name,\n status: tm.status,\n artifact: tm.artifact,\n tool_call_id: tm.tool_call_id,\n response_metadata: tm.response_metadata,\n additional_kwargs: stripFrameworkRefMetadata(tm.additional_kwargs),\n content,\n });\n}\n\n/**\n * Returns a copy of `kwargs` with `_refKey`, `_refScope`, and\n * `_unresolvedRefs` removed. Returns the input reference-equal when\n * none of those keys are present so the no-strip path stays cheap;\n * returns `undefined` when stripping leaves the object empty so the\n * caller can drop the field entirely.\n */\nfunction stripFrameworkRefMetadata(\n kwargs: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (kwargs == null) return undefined;\n if (\n !('_refKey' in kwargs) &&\n !('_refScope' in kwargs) &&\n !('_unresolvedRefs' in kwargs)\n ) {\n return kwargs;\n }\n const { _refKey, _refScope, _unresolvedRefs, ...rest } = kwargs as Record<\n string,\n unknown\n > & {\n _refKey?: unknown;\n _refScope?: unknown;\n _unresolvedRefs?: unknown;\n };\n void _refKey;\n void _refScope;\n void _unresolvedRefs;\n return Object.keys(rest).length === 0 ? undefined : rest;\n}\n"],"mappings":"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"}
|
|
@@ -20,6 +20,25 @@ export declare function cloneMessage<T extends MessageWithContent>(message: T, c
|
|
|
20
20
|
* @returns - A new array of message objects with cache control added.
|
|
21
21
|
*/
|
|
22
22
|
export declare function addCacheControl<T extends AnthropicMessage | BaseMessage>(messages: T[]): T[];
|
|
23
|
+
/**
|
|
24
|
+
* Anthropic API: single tail cache breakpoint (default strategy).
|
|
25
|
+
*
|
|
26
|
+
* Places exactly ONE `cache_control` marker on the last cacheable block of the
|
|
27
|
+
* final non-synthetic message, mirroring the Claude Code strategy
|
|
28
|
+
* (`markerIndex = messages.length - 1`). Because the marker always rides the
|
|
29
|
+
* true tail, the entire conversation prefix is written once and read back on
|
|
30
|
+
* the next turn as the history grows append-only — instead of the rolling
|
|
31
|
+
* "last two user messages" markers, which leave freshly appended tool/assistant
|
|
32
|
+
* turns outside the cached prefix and re-write large spans every step.
|
|
33
|
+
*
|
|
34
|
+
* Stale markers (Anthropic `cache_control` and Bedrock cache points) are
|
|
35
|
+
* stripped from every message in a single backward pass so exactly one marker
|
|
36
|
+
* survives. Synthetic skill/meta messages are skipped as anchors (their volatile
|
|
37
|
+
* content must not pin the cache) but still have stale markers removed.
|
|
38
|
+
*
|
|
39
|
+
* Returns a new array; only messages that require modification are cloned.
|
|
40
|
+
*/
|
|
41
|
+
export declare function addTailCacheControl<T extends AnthropicMessage | BaseMessage>(messages: T[]): T[];
|
|
23
42
|
export declare function addCacheControlToStablePrefixMessages<T extends AnthropicMessage | BaseMessage>(messages: T[], maxCachePoints: number): T[];
|
|
24
43
|
/**
|
|
25
44
|
* Removes all Anthropic cache_control fields from messages
|
|
@@ -48,4 +67,25 @@ export declare function addBedrockCacheControl<T extends MessageWithContent & {
|
|
|
48
67
|
getType?: () => string;
|
|
49
68
|
role?: string;
|
|
50
69
|
}>(messages: T[]): T[];
|
|
70
|
+
/**
|
|
71
|
+
* Bedrock Converse API: single tail cache breakpoint (default strategy).
|
|
72
|
+
*
|
|
73
|
+
* The Bedrock counterpart of {@link addTailCacheControl}. Strips ALL existing
|
|
74
|
+
* cache control (Bedrock cache points and Anthropic `cache_control`) from every
|
|
75
|
+
* message, then inserts exactly ONE `{ cachePoint: { type: 'default' } }` block
|
|
76
|
+
* immediately after the last non-empty text block of the most recent
|
|
77
|
+
* non-synthetic, non-system message. Anchoring on the rolling tail keeps the
|
|
78
|
+
* cached prefix append-only as the conversation grows, instead of re-writing
|
|
79
|
+
* large spans every turn with the legacy "last two user messages" cache points.
|
|
80
|
+
*
|
|
81
|
+
* System messages are sanitized (Anthropic `cache_control` stripped) but never
|
|
82
|
+
* anchored. Synthetic skill/meta messages are skipped as anchors so their
|
|
83
|
+
* volatile content cannot pin the cache.
|
|
84
|
+
*
|
|
85
|
+
* Returns a new array - only clones messages that require modification.
|
|
86
|
+
*/
|
|
87
|
+
export declare function addBedrockTailCacheControl<T extends MessageWithContent & {
|
|
88
|
+
getType?: () => string;
|
|
89
|
+
role?: string;
|
|
90
|
+
}>(messages: T[]): T[];
|
|
51
91
|
export {};
|
|
@@ -407,6 +407,8 @@ export interface LangfuseConfig {
|
|
|
407
407
|
publicKey?: string;
|
|
408
408
|
secretKey?: string;
|
|
409
409
|
baseUrl?: string;
|
|
410
|
+
metadata?: Record<string, string | number | boolean | null | undefined>;
|
|
411
|
+
tags?: string[];
|
|
410
412
|
toolNodeTracing?: LangfuseToolNodeTracingConfig;
|
|
411
413
|
toolOutputTracing?: LangfuseToolOutputTracingConfig;
|
|
412
414
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.37",
|
|
4
4
|
"main": "./dist/cjs/main.cjs",
|
|
5
5
|
"module": "./dist/esm/main.mjs",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -139,6 +139,7 @@
|
|
|
139
139
|
"tool": "node --trace-warnings -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tools.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
|
|
140
140
|
"search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/search.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
|
|
141
141
|
"tool_search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tool_search.ts",
|
|
142
|
+
"bench:cache": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/bench-prompt-cache.ts",
|
|
142
143
|
"subagent": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-subagent.ts",
|
|
143
144
|
"subagent:events": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/subagent-event-driven-debug.ts",
|
|
144
145
|
"subagent:tools": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/subagent-tools-debug.ts",
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
Providers,
|
|
17
17
|
} from '@/common';
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
addTailCacheControl,
|
|
20
20
|
addCacheControlToStablePrefixMessages,
|
|
21
21
|
cloneMessage,
|
|
22
22
|
} from '@/messages/cache';
|
|
@@ -689,7 +689,7 @@ export class AgentContext {
|
|
|
689
689
|
dynamicTail.length === 0 &&
|
|
690
690
|
body.length >= 2
|
|
691
691
|
) {
|
|
692
|
-
body =
|
|
692
|
+
body = addTailCacheControl(body);
|
|
693
693
|
}
|
|
694
694
|
return [...prefix, ...body];
|
|
695
695
|
}).withConfig({ runName: 'prompt' });
|