@librechat/agents 3.4.7 → 3.5.1
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 +0 -356
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/agents/projection.cjs +0 -9
- package/dist/cjs/agents/projection.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +0 -26
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +0 -85
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +0 -14
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +1 -909
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +7 -272
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -1
- package/dist/cjs/graphs/createGraph.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestion.cjs +0 -53
- package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestions.cjs +0 -20
- package/dist/cjs/hitl/askUserQuestions.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs +0 -6
- package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +0 -127
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/createToolPolicyHook.cjs +0 -38
- package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +0 -77
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +0 -79
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +0 -16
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/matchers.cjs +0 -104
- package/dist/cjs/hooks/matchers.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +0 -8
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +0 -14
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +0 -75
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseConfig.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeContext.cjs +0 -13
- package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeScope.cjs +0 -4
- package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
- package/dist/cjs/langfuseSpanRegistry.cjs +0 -28
- package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +0 -3
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +0 -74
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +0 -3
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -111
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +0 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs +0 -8
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/cachePoints.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +1 -95
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +0 -78
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +0 -24
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/contextOverflowRecovery.cjs +0 -62
- package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -1
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +2 -21
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +0 -35
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +0 -9
- package/dist/cjs/llm/init.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +1 -270
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/mistral/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +0 -99
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/streamMetadata.cjs +0 -21
- package/dist/cjs/llm/openai/streamMetadata.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +0 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +0 -6
- package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/preempt.cjs +0 -75
- package/dist/cjs/llm/preempt.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/llm/request.cjs +0 -10
- package/dist/cjs/llm/request.cjs.map +1 -1
- package/dist/cjs/llm/stream/chunkAdapters.cjs +0 -62
- package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -1
- package/dist/cjs/llm/stream/smoother.cjs +0 -58
- package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
- package/dist/cjs/llm/streamLimits.cjs +4 -293
- package/dist/cjs/llm/streamLimits.cjs.map +1 -1
- package/dist/cjs/llm/truncation.cjs +0 -36
- package/dist/cjs/llm/truncation.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +0 -330
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/messages/alternation.cjs +0 -43
- package/dist/cjs/messages/alternation.cjs.map +1 -1
- package/dist/cjs/messages/anthropicToolCache.cjs +0 -45
- package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
- package/dist/cjs/messages/assistantPhase.cjs +0 -12
- package/dist/cjs/messages/assistantPhase.cjs.map +1 -1
- package/dist/cjs/messages/budget.cjs +0 -9
- package/dist/cjs/messages/budget.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +0 -172
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/content.cjs +0 -9
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +0 -15
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/contextPruningSettings.cjs +0 -3
- package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +0 -48
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +1 -345
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/handoffCue.cjs +0 -36
- package/dist/cjs/messages/handoffCue.cjs.map +1 -1
- package/dist/cjs/messages/ids.cjs.map +1 -1
- package/dist/cjs/messages/injected.cjs +0 -33
- package/dist/cjs/messages/injected.cjs.map +1 -1
- package/dist/cjs/messages/langchain.cjs +0 -14
- package/dist/cjs/messages/langchain.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +1 -184
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +0 -32
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/messages/reducer.cjs +0 -30
- package/dist/cjs/messages/reducer.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs +0 -14
- package/dist/cjs/messages/tools.cjs.map +1 -1
- package/dist/cjs/openai/index.cjs.map +1 -1
- package/dist/cjs/prompts/activityLabel.cjs +9 -79
- package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
- package/dist/cjs/responses/index.cjs.map +1 -1
- package/dist/cjs/run.cjs +2 -538
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1 -17
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +0 -7
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/session/ids.cjs.map +1 -1
- package/dist/cjs/session/messageSerialization.cjs +0 -4
- package/dist/cjs/session/messageSerialization.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -93
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/index.cjs +0 -27
- package/dist/cjs/summarization/index.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +0 -273
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +0 -35
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +0 -24
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/Calculator.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +0 -13
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +0 -101
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ReadFile.cjs.map +1 -1
- package/dist/cjs/tools/SkillTool.cjs +0 -5
- package/dist/cjs/tools/SkillTool.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +0 -10
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +6 -1038
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +0 -179
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +0 -39
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +0 -6
- package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +1 -41
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/intentArg.cjs +1 -132
- package/dist/cjs/tools/intentArg.cjs.map +1 -1
- package/dist/cjs/tools/local/CompileCheckTool.cjs +0 -25
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
- package/dist/cjs/tools/local/FileCheckpointer.cjs +0 -23
- package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +0 -80
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +0 -126
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +0 -12
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/local/attachments.cjs +0 -44
- package/dist/cjs/tools/local/attachments.cjs.map +1 -1
- package/dist/cjs/tools/local/bashAst.cjs +0 -16
- package/dist/cjs/tools/local/bashAst.cjs.map +1 -1
- package/dist/cjs/tools/local/editStrategies.cjs +0 -14
- package/dist/cjs/tools/local/editStrategies.cjs.map +1 -1
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/local/syntaxCheck.cjs +0 -31
- package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
- package/dist/cjs/tools/local/textEncoding.cjs +0 -11
- package/dist/cjs/tools/local/textEncoding.cjs.map +1 -1
- package/dist/cjs/tools/local/workspaceFS.cjs +0 -33
- package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
- package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
- package/dist/cjs/tools/schema.cjs +0 -8
- package/dist/cjs/tools/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/anthropic.cjs +0 -9
- package/dist/cjs/tools/search/anthropic.cjs.map +1 -1
- package/dist/cjs/tools/search/content.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +0 -33
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-search.cjs +0 -2
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +0 -25
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/format.cjs +0 -30
- package/dist/cjs/tools/search/format.cjs.map +1 -1
- package/dist/cjs/tools/search/highlights.cjs +0 -16
- package/dist/cjs/tools/search/highlights.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +0 -8
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-search.cjs +0 -7
- package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +0 -35
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +0 -1
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +1 -14
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs +0 -46
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +0 -25
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +0 -8
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/cjs/tools/skillCatalog.cjs +0 -6
- package/dist/cjs/tools/skillCatalog.cjs.map +1 -1
- package/dist/cjs/tools/streamedToolCallSeals.cjs +0 -6
- package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +0 -2
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -213
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +0 -4
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +1 -322
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/types/hitl.cjs +0 -5
- package/dist/cjs/types/hitl.cjs.map +1 -1
- package/dist/cjs/utils/callbacks.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +14 -165
- package/dist/cjs/utils/errors.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs +0 -10
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/graph.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs +0 -3
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs +0 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/misc.cjs +0 -16
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/cjs/utils/proxy.cjs +0 -26
- package/dist/cjs/utils/proxy.cjs.map +1 -1
- package/dist/cjs/utils/run.cjs +0 -6
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/cjs/utils/schema.cjs +0 -5
- package/dist/cjs/utils/schema.cjs.map +1 -1
- package/dist/cjs/utils/title.cjs +0 -6
- package/dist/cjs/utils/title.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +0 -113
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +0 -41
- package/dist/cjs/utils/toolContent.cjs.map +1 -1
- package/dist/cjs/utils/truncation.cjs +0 -61
- package/dist/cjs/utils/truncation.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +0 -356
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/agents/projection.mjs +0 -9
- package/dist/esm/agents/projection.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +0 -26
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +0 -85
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +0 -14
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +1 -909
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +7 -272
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -1
- package/dist/esm/graphs/createGraph.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestion.mjs +0 -53
- package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestions.mjs +0 -20
- package/dist/esm/hitl/askUserQuestions.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestionsInterrupt.mjs +0 -6
- package/dist/esm/hitl/askUserQuestionsInterrupt.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +0 -127
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/createToolPolicyHook.mjs +0 -38
- package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +0 -77
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +0 -79
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +0 -16
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/matchers.mjs +0 -104
- package/dist/esm/hooks/matchers.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +0 -8
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +0 -14
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +0 -75
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseConfig.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeContext.mjs +0 -13
- package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeScope.mjs +0 -4
- package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
- package/dist/esm/langfuseSpanRegistry.mjs +0 -28
- package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +0 -3
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +0 -74
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +0 -3
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -111
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +0 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/stream_events.mjs +0 -8
- package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
- package/dist/esm/llm/bedrock/cachePoints.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +1 -95
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +0 -78
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +0 -24
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/contextOverflowRecovery.mjs +0 -62
- package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -1
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +2 -21
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +0 -35
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +0 -9
- package/dist/esm/llm/init.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +1 -270
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/mistral/index.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +0 -99
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/streamMetadata.mjs +0 -21
- package/dist/esm/llm/openai/streamMetadata.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +0 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +0 -6
- package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
- package/dist/esm/llm/preempt.mjs +0 -75
- package/dist/esm/llm/preempt.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/llm/request.mjs +0 -10
- package/dist/esm/llm/request.mjs.map +1 -1
- package/dist/esm/llm/stream/chunkAdapters.mjs +0 -62
- package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -1
- package/dist/esm/llm/stream/smoother.mjs +0 -58
- package/dist/esm/llm/stream/smoother.mjs.map +1 -1
- package/dist/esm/llm/streamLimits.mjs +4 -293
- package/dist/esm/llm/streamLimits.mjs.map +1 -1
- package/dist/esm/llm/truncation.mjs +0 -36
- package/dist/esm/llm/truncation.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +0 -330
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/messages/alternation.mjs +0 -43
- package/dist/esm/messages/alternation.mjs.map +1 -1
- package/dist/esm/messages/anthropicToolCache.mjs +0 -45
- package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
- package/dist/esm/messages/assistantPhase.mjs +0 -12
- package/dist/esm/messages/assistantPhase.mjs.map +1 -1
- package/dist/esm/messages/budget.mjs +0 -9
- package/dist/esm/messages/budget.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +0 -172
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/content.mjs +0 -9
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +0 -15
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/contextPruningSettings.mjs +0 -3
- package/dist/esm/messages/contextPruningSettings.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +0 -48
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +1 -345
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/handoffCue.mjs +0 -36
- package/dist/esm/messages/handoffCue.mjs.map +1 -1
- package/dist/esm/messages/ids.mjs.map +1 -1
- package/dist/esm/messages/injected.mjs +0 -33
- package/dist/esm/messages/injected.mjs.map +1 -1
- package/dist/esm/messages/langchain.mjs +0 -14
- package/dist/esm/messages/langchain.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1 -184
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +0 -32
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/messages/reducer.mjs +0 -30
- package/dist/esm/messages/reducer.mjs.map +1 -1
- package/dist/esm/messages/tools.mjs +0 -14
- package/dist/esm/messages/tools.mjs.map +1 -1
- package/dist/esm/openai/index.mjs.map +1 -1
- package/dist/esm/prompts/activityLabel.mjs +9 -79
- package/dist/esm/prompts/activityLabel.mjs.map +1 -1
- package/dist/esm/responses/index.mjs.map +1 -1
- package/dist/esm/run.mjs +2 -538
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1 -17
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +0 -7
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/session/ids.mjs.map +1 -1
- package/dist/esm/session/messageSerialization.mjs +0 -4
- package/dist/esm/session/messageSerialization.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -93
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/index.mjs +0 -27
- package/dist/esm/summarization/index.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +0 -273
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +0 -35
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +0 -24
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/Calculator.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +0 -13
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +0 -101
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ReadFile.mjs.map +1 -1
- package/dist/esm/tools/SkillTool.mjs +0 -5
- package/dist/esm/tools/SkillTool.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +0 -10
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +6 -1038
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +0 -179
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +0 -39
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +0 -6
- package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +1 -41
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/intentArg.mjs +1 -132
- package/dist/esm/tools/intentArg.mjs.map +1 -1
- package/dist/esm/tools/local/CompileCheckTool.mjs +0 -25
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
- package/dist/esm/tools/local/FileCheckpointer.mjs +0 -23
- package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +0 -80
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +0 -126
- package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +0 -12
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/local/attachments.mjs +0 -44
- package/dist/esm/tools/local/attachments.mjs.map +1 -1
- package/dist/esm/tools/local/bashAst.mjs +0 -16
- package/dist/esm/tools/local/bashAst.mjs.map +1 -1
- package/dist/esm/tools/local/editStrategies.mjs +0 -14
- package/dist/esm/tools/local/editStrategies.mjs.map +1 -1
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/local/syntaxCheck.mjs +0 -31
- package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
- package/dist/esm/tools/local/textEncoding.mjs +0 -11
- package/dist/esm/tools/local/textEncoding.mjs.map +1 -1
- package/dist/esm/tools/local/workspaceFS.mjs +0 -33
- package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
- package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
- package/dist/esm/tools/schema.mjs +0 -8
- package/dist/esm/tools/schema.mjs.map +1 -1
- package/dist/esm/tools/search/anthropic.mjs +0 -9
- package/dist/esm/tools/search/anthropic.mjs.map +1 -1
- package/dist/esm/tools/search/content.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +0 -33
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/crw-search.mjs +0 -2
- package/dist/esm/tools/search/crw-search.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +0 -25
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/format.mjs +0 -30
- package/dist/esm/tools/search/format.mjs.map +1 -1
- package/dist/esm/tools/search/highlights.mjs +0 -16
- package/dist/esm/tools/search/highlights.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +0 -8
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-search.mjs +0 -7
- package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +0 -35
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +0 -1
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +1 -14
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs +0 -46
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +0 -25
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +0 -8
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/esm/tools/skillCatalog.mjs +0 -6
- package/dist/esm/tools/skillCatalog.mjs.map +1 -1
- package/dist/esm/tools/streamedToolCallSeals.mjs +0 -6
- package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +0 -2
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -213
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +0 -4
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +1 -322
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/types/hitl.mjs +0 -5
- package/dist/esm/types/hitl.mjs.map +1 -1
- package/dist/esm/utils/callbacks.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +14 -165
- package/dist/esm/utils/errors.mjs.map +1 -1
- package/dist/esm/utils/events.mjs +0 -10
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/graph.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs +0 -3
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs +0 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/misc.mjs +0 -16
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/esm/utils/proxy.mjs +0 -26
- package/dist/esm/utils/proxy.mjs.map +1 -1
- package/dist/esm/utils/run.mjs +0 -6
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/esm/utils/schema.mjs +0 -5
- package/dist/esm/utils/schema.mjs.map +1 -1
- package/dist/esm/utils/title.mjs +0 -6
- package/dist/esm/utils/title.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +0 -113
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +0 -41
- package/dist/esm/utils/toolContent.mjs.map +1 -1
- package/dist/esm/utils/truncation.mjs +0 -61
- package/dist/esm/utils/truncation.mjs.map +1 -1
- package/package.json +35 -32
- package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +0 -52
- package/dist/types/test/mockTools.d.ts +0 -28
- package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +0 -40
- package/src/__tests__/stream.eagerArgsDivergence.test.ts +0 -911
- package/src/__tests__/stream.eagerEventExecution.test.ts +0 -5046
- package/src/__tests__/stream.streamLimits.test.ts +0 -1982
- package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +0 -448
- package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +0 -655
- package/src/agents/__tests__/AgentContext.openrouter.live.test.ts +0 -128
- package/src/agents/__tests__/AgentContext.overflow.test.ts +0 -205
- package/src/agents/__tests__/AgentContext.test.ts +0 -2425
- package/src/agents/__tests__/projection.test.ts +0 -73
- package/src/agents/__tests__/promptCacheLiveHelpers.ts +0 -175
- package/src/aggregator.test.ts +0 -1214
- package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +0 -273
- package/src/graphs/__tests__/Graph.closeRunStep.test.ts +0 -385
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +0 -1766
- package/src/graphs/__tests__/Graph.preemptSignal.test.ts +0 -126
- package/src/graphs/__tests__/Graph.reasoning.test.ts +0 -1717
- package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +0 -80
- package/src/graphs/__tests__/MultiAgentGraph.test.ts +0 -177
- package/src/graphs/__tests__/composition.smoke.test.ts +0 -509
- package/src/graphs/__tests__/createGraph.test.ts +0 -206
- package/src/hooks/__tests__/HookRegistry.test.ts +0 -334
- package/src/hooks/__tests__/compactHooks.test.ts +0 -250
- package/src/hooks/__tests__/createToolPolicyHook.test.ts +0 -268
- package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +0 -405
- package/src/hooks/__tests__/executeHooks.test.ts +0 -1294
- package/src/hooks/__tests__/integration.test.ts +0 -337
- package/src/hooks/__tests__/matchers.test.ts +0 -238
- package/src/hooks/__tests__/preemptBoundary.test.ts +0 -152
- package/src/hooks/__tests__/toolHooks.test.ts +0 -669
- package/src/llm/__tests__/contextOverflowRecovery.test.ts +0 -401
- package/src/llm/__tests__/fallbackOverflow.test.ts +0 -287
- package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
- package/src/llm/anthropic/inherited-content-utils.spec.ts +0 -249
- package/src/llm/anthropic/inherited-stream-events.spec.ts +0 -1259
- package/src/llm/anthropic/inherited-strict.spec.ts +0 -302
- package/src/llm/anthropic/llm.spec.ts +0 -3394
- package/src/llm/anthropic/utils/cross-provider-reasoning.test.ts +0 -383
- package/src/llm/anthropic/utils/cross-provider-server-tools.test.ts +0 -110
- package/src/llm/anthropic/utils/server-tool-inputs.test.ts +0 -508
- package/src/llm/anthropic/utils/streaming-tool-input.test.ts +0 -361
- package/src/llm/anthropic/utils/stripPrefillCache.test.ts +0 -111
- package/src/llm/anthropic/utils/tool-id-normalization.test.ts +0 -287
- package/src/llm/bedrock/inherited-cache.spec.ts +0 -144
- package/src/llm/bedrock/inherited.spec.ts +0 -724
- package/src/llm/bedrock/llm.spec.ts +0 -1533
- package/src/llm/bedrock/streamSealDispatch.test.ts +0 -331
- package/src/llm/bedrock/toolCache.test.ts +0 -194
- package/src/llm/bedrock/utils/cross-provider-reasoning.test.ts +0 -131
- package/src/llm/bedrock/utils/cross-provider-server-tools.test.ts +0 -122
- package/src/llm/bedrock/utils/message_inputs.test.ts +0 -650
- package/src/llm/bedrock/utils/message_outputs.test.ts +0 -85
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +0 -118
- package/src/llm/custom-chat-models.smoke.test.ts +0 -1966
- package/src/llm/google/data/gettysburg10.wav +0 -0
- package/src/llm/google/data/hotdog.jpg +0 -0
- package/src/llm/google/inherited-stream-events.spec.ts +0 -350
- package/src/llm/google/llm.spec.ts +0 -1388
- package/src/llm/google/streamSmoothing.test.ts +0 -121
- package/src/llm/google/utils/common.test.ts +0 -196
- package/src/llm/invoke.alternation.test.ts +0 -87
- package/src/llm/invoke.handoffCue.test.ts +0 -112
- package/src/llm/invoke.streamLimits.test.ts +0 -142
- package/src/llm/invoke.test.ts +0 -1198
- package/src/llm/mistral/streamSmoothing.test.ts +0 -97
- package/src/llm/openai/cacheWriteTokens.test.ts +0 -112
- package/src/llm/openai/contentBlocks.test.ts +0 -381
- package/src/llm/openai/deepseek.test.ts +0 -747
- package/src/llm/openai/inherited-deepseek.spec.ts +0 -347
- package/src/llm/openai/inherited-xai.spec.ts +0 -416
- package/src/llm/openai/llm.spec.ts +0 -1774
- package/src/llm/openai/managedRequests.test.ts +0 -182
- package/src/llm/openai/sequentialToolCallSeals.test.ts +0 -199
- package/src/llm/openai/streamMetadata.spec.ts +0 -86
- package/src/llm/openai/streamMetadataDedup.spec.ts +0 -166
- package/src/llm/openai/utils/isReasoningModel.test.ts +0 -90
- package/src/llm/openai/utils/messages.test.ts +0 -488
- package/src/llm/openrouter/reasoning.test.ts +0 -561
- package/src/llm/openrouter/toolCache.test.ts +0 -134
- package/src/llm/preempt.test.ts +0 -323
- package/src/llm/stream/chunkAdapters.test.ts +0 -202
- package/src/llm/stream/reassembly.test.ts +0 -241
- package/src/llm/stream/smoother.bench.test.ts +0 -155
- package/src/llm/stream/smoother.test.ts +0 -519
- package/src/llm/streamLimits.test.ts +0 -450
- package/src/llm/truncation.test.ts +0 -242
- package/src/llm/vertexai/fixThoughtSignatures.test.ts +0 -154
- package/src/llm/vertexai/inherited-stream-events.spec.ts +0 -271
- package/src/llm/vertexai/llm.spec.ts +0 -159
- package/src/llm/vertexai/repairUsageMetadata.test.ts +0 -54
- package/src/llm/vertexai/sealStreamedToolCalls.test.ts +0 -88
- package/src/llm/vertexai/streamSealDispatch.test.ts +0 -148
- package/src/llm/vertexai/streamSmoothing.test.ts +0 -109
- package/src/messages/__tests__/anthropicToolCache.test.ts +0 -339
- package/src/messages/__tests__/observationMasking.test.ts +0 -312
- package/src/messages/__tests__/recency.test.ts +0 -267
- package/src/messages/__tests__/tools.test.ts +0 -473
- package/src/messages/alternation.test.ts +0 -212
- package/src/messages/assistantPhase.test.ts +0 -75
- package/src/messages/cache.tail.test.ts +0 -533
- package/src/messages/cache.test.ts +0 -2166
- package/src/messages/content.test.ts +0 -362
- package/src/messages/contextPruning.test.ts +0 -184
- package/src/messages/ensureThinkingBlock.test.ts +0 -1427
- package/src/messages/foldToollessToolBlocks.test.ts +0 -596
- package/src/messages/formatAgentMessages.reducer.test.ts +0 -162
- package/src/messages/formatAgentMessages.skills.test.ts +0 -513
- package/src/messages/formatAgentMessages.steer.test.ts +0 -598
- package/src/messages/formatAgentMessages.test.ts +0 -8111
- package/src/messages/formatAgentMessages.tools.test.ts +0 -522
- package/src/messages/formatMessage.test.ts +0 -693
- package/src/messages/handoffCue.test.ts +0 -96
- package/src/messages/injected.test.ts +0 -107
- package/src/messages/labelContentByAgent.test.ts +0 -962
- package/src/messages/reducer.spec.ts +0 -32
- package/src/messages/shiftIndexTokenCountMap.test.ts +0 -81
- package/src/messages/tailCacheConversion.test.ts +0 -161
- package/src/openai/__tests__/openai.test.ts +0 -337
- package/src/responses/__tests__/responses.test.ts +0 -652
- package/src/scripts/abort.ts +0 -157
- package/src/scripts/activity-labels/captured.json +0 -56
- package/src/scripts/activity-labels/checks.cjs +0 -205
- package/src/scripts/activity-labels/corpus.cjs +0 -473
- package/src/scripts/activity-labels/report.cjs +0 -203
- package/src/scripts/activity-labels/rescore.cjs +0 -102
- package/src/scripts/activity-labels/run.ts +0 -705
- package/src/scripts/activity-labels/variants.ts +0 -71
- package/src/scripts/ant_web_search.ts +0 -159
- package/src/scripts/ant_web_search_edge_case.ts +0 -163
- package/src/scripts/ant_web_search_error_edge_case.ts +0 -149
- package/src/scripts/args.ts +0 -48
- package/src/scripts/bedrock-cache-debug.ts +0 -250
- package/src/scripts/bedrock-content-aggregation-test.ts +0 -266
- package/src/scripts/bedrock-merge-test.ts +0 -107
- package/src/scripts/bedrock-parallel-tools-test.ts +0 -204
- package/src/scripts/bench-prompt-cache.ts +0 -479
- package/src/scripts/caching.ts +0 -132
- package/src/scripts/cli.ts +0 -172
- package/src/scripts/cli2.ts +0 -133
- package/src/scripts/cli3.ts +0 -184
- package/src/scripts/cli4.ts +0 -191
- package/src/scripts/cli5.ts +0 -191
- package/src/scripts/code_exec.ts +0 -214
- package/src/scripts/code_exec_files.ts +0 -237
- package/src/scripts/code_exec_multi_session.ts +0 -237
- package/src/scripts/code_exec_ptc.ts +0 -335
- package/src/scripts/code_exec_session.ts +0 -283
- package/src/scripts/code_exec_simple.ts +0 -148
- package/src/scripts/compare_pi_vs_ours.ts +0 -1259
- package/src/scripts/content.ts +0 -139
- package/src/scripts/context-overflow-probe.ts +0 -997
- package/src/scripts/empty_input.ts +0 -137
- package/src/scripts/handoff-test.ts +0 -134
- package/src/scripts/local_engine.ts +0 -166
- package/src/scripts/local_engine_checkpointer.ts +0 -205
- package/src/scripts/local_engine_compile.ts +0 -263
- package/src/scripts/local_engine_hooks.ts +0 -226
- package/src/scripts/local_engine_image.ts +0 -201
- package/src/scripts/local_engine_ptc.ts +0 -151
- package/src/scripts/local_engine_workspace.ts +0 -258
- package/src/scripts/memory.ts +0 -107
- package/src/scripts/multi-agent-chain.ts +0 -332
- package/src/scripts/multi-agent-conditional.ts +0 -222
- package/src/scripts/multi-agent-document-review-chain.ts +0 -198
- package/src/scripts/multi-agent-hybrid-flow.ts +0 -311
- package/src/scripts/multi-agent-parallel-start.ts +0 -265
- package/src/scripts/multi-agent-parallel.ts +0 -395
- package/src/scripts/multi-agent-sequence.ts +0 -217
- package/src/scripts/multi-agent-subagent.ts +0 -246
- package/src/scripts/multi-agent-supervisor.ts +0 -366
- package/src/scripts/multi-agent-test.ts +0 -187
- package/src/scripts/openrouter_prompt_cache_live.ts +0 -310
- package/src/scripts/parallel-asymmetric-tools-test.ts +0 -275
- package/src/scripts/parallel-full-metadata-test.ts +0 -241
- package/src/scripts/parallel-tools-test.ts +0 -341
- package/src/scripts/preempt-probe.ts +0 -330
- package/src/scripts/preempt-scenarios.ts +0 -388
- package/src/scripts/programmatic_exec.ts +0 -387
- package/src/scripts/programmatic_exec_agent.ts +0 -232
- package/src/scripts/search.ts +0 -147
- package/src/scripts/sequential-full-metadata-test.ts +0 -198
- package/src/scripts/session_live.ts +0 -548
- package/src/scripts/simple.ts +0 -226
- package/src/scripts/single-agent-metadata-test.ts +0 -196
- package/src/scripts/stream.ts +0 -141
- package/src/scripts/subagent-configurable-inheritance.ts +0 -252
- package/src/scripts/subagent-event-driven-debug.ts +0 -190
- package/src/scripts/subagent-tools-debug.ts +0 -160
- package/src/scripts/subagent-usage-sink.ts +0 -176
- package/src/scripts/summarization-recency.ts +0 -462
- package/src/scripts/test-custom-prompt-key.ts +0 -145
- package/src/scripts/test-handoff-input.ts +0 -170
- package/src/scripts/test-handoff-preamble.ts +0 -278
- package/src/scripts/test-handoff-steering.ts +0 -430
- package/src/scripts/test-multi-agent-list-handoff.ts +0 -418
- package/src/scripts/test-parallel-agent-labeling.ts +0 -327
- package/src/scripts/test-parallel-handoffs.ts +0 -292
- package/src/scripts/test-thinking-handoff-bedrock.ts +0 -154
- package/src/scripts/test-thinking-handoff.ts +0 -156
- package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +0 -166
- package/src/scripts/test-tool-before-handoff-role-order.ts +0 -276
- package/src/scripts/test-tools-before-handoff.ts +0 -222
- package/src/scripts/test_code_api.ts +0 -354
- package/src/scripts/thinking-bedrock.ts +0 -160
- package/src/scripts/thinking-vertexai.ts +0 -168
- package/src/scripts/thinking.ts +0 -172
- package/src/scripts/tool_search.ts +0 -153
- package/src/scripts/tools.ts +0 -163
- package/src/session/__tests__/JsonlSessionStore.test.ts +0 -1463
- package/src/session/__tests__/handlers.test.ts +0 -161
- package/src/specs/activity-label-observability.live.test.ts +0 -262
- package/src/specs/activity-label-observability.test.ts +0 -167
- package/src/specs/activity-label-prompt.test.ts +0 -359
- package/src/specs/activity-label-trace-seed.test.ts +0 -47
- package/src/specs/activity-phase-label.test.ts +0 -306
- package/src/specs/agent-handoffs.live.test.ts +0 -140
- package/src/specs/agent-handoffs.test.ts +0 -2465
- package/src/specs/anthropic.simple.test.ts +0 -453
- package/src/specs/ask-user-question-batch.test.ts +0 -289
- package/src/specs/ask-user-questions.live.test.ts +0 -185
- package/src/specs/ask-user-questions.test.ts +0 -293
- package/src/specs/azure.simple.test.ts +0 -502
- package/src/specs/bedrock-toolless.live.test.ts +0 -123
- package/src/specs/bedrock-truncation.live.test.ts +0 -191
- package/src/specs/cache.simple.test.ts +0 -416
- package/src/specs/context-accuracy.live.test.ts +0 -409
- package/src/specs/context-overflow-recovery.live.test.ts +0 -213
- package/src/specs/context-usage-event.test.ts +0 -117
- package/src/specs/context-usage.live.test.ts +0 -297
- package/src/specs/custom-event-await.test.ts +0 -304
- package/src/specs/deepseek.simple.test.ts +0 -290
- package/src/specs/deterministic-trace-id.test.ts +0 -43
- package/src/specs/discovered-tools.test.ts +0 -217
- package/src/specs/durability-checkpoint.integration.test.ts +0 -243
- package/src/specs/graph-subagent.live.test.ts +0 -242
- package/src/specs/graph-subagent.test.ts +0 -322
- package/src/specs/handoffCue.test.ts +0 -165
- package/src/specs/langfuse-callbacks.test.ts +0 -1578
- package/src/specs/langfuse-config.test.ts +0 -247
- package/src/specs/langfuse-instrumentation.test.ts +0 -393
- package/src/specs/langfuse-metadata.test.ts +0 -245
- package/src/specs/langfuse-routing.integration.test.ts +0 -844
- package/src/specs/langfuse-runtime-context.test.ts +0 -92
- package/src/specs/langfuse-span-registry.test.ts +0 -87
- package/src/specs/langfuse-tool-output-tracing.test.ts +0 -1858
- package/src/specs/langfuse-trace-shaping.test.ts +0 -591
- package/src/specs/moonshot.simple.test.ts +0 -367
- package/src/specs/multi-agent-summarization.test.ts +0 -396
- package/src/specs/openai.simple.test.ts +0 -315
- package/src/specs/openrouter.simple.test.ts +0 -275
- package/src/specs/preemptSeal.test.ts +0 -678
- package/src/specs/prune.test.ts +0 -3741
- package/src/specs/reasoning.test.ts +0 -201
- package/src/specs/run-step-timestamps.test.ts +0 -414
- package/src/specs/spec.utils.ts +0 -15
- package/src/specs/subagent.test.ts +0 -864
- package/src/specs/summarization-unit.test.ts +0 -973
- package/src/specs/summarization.test.ts +0 -3823
- package/src/specs/summarize-prune.test.ts +0 -376
- package/src/specs/thinking-handoff.test.ts +0 -621
- package/src/specs/thinking-prune.test.ts +0 -827
- package/src/specs/token-accounting-e2e.test.ts +0 -1034
- package/src/specs/token-accounting-pipeline.test.ts +0 -1008
- package/src/specs/token-distribution-edge-case.test.ts +0 -315
- package/src/specs/token-memoization.test.ts +0 -40
- package/src/specs/tokens.test.ts +0 -1000
- package/src/specs/tool-error-resume.test.ts +0 -194
- package/src/specs/tool-error.test.ts +0 -198
- package/src/specs/vllm-reasoning-toolcalls.test.ts +0 -340
- package/src/stream.dispatch.test.ts +0 -63
- package/src/stream.test.ts +0 -144
- package/src/summarization/__tests__/aggregator.test.ts +0 -236
- package/src/summarization/__tests__/node.test.ts +0 -1996
- package/src/summarization/__tests__/trigger.test.ts +0 -149
- package/src/summarization/chunkHandler.test.ts +0 -196
- package/src/test/mockTools.ts +0 -386
- package/src/tools/Calculator.test.ts +0 -278
- package/src/tools/__tests__/BashExecutor.test.ts +0 -108
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +0 -939
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +0 -858
- package/src/tools/__tests__/CodeExecutor.stateful.test.ts +0 -124
- package/src/tools/__tests__/LocalExecutionRoots.test.ts +0 -8
- package/src/tools/__tests__/LocalExecutionTools.test.ts +0 -2733
- package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +0 -318
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -1557
- package/src/tools/__tests__/ReadFile.test.ts +0 -44
- package/src/tools/__tests__/SkillTool.test.ts +0 -442
- package/src/tools/__tests__/SubagentExecutor.test.ts +0 -3728
- package/src/tools/__tests__/SubagentReplay.test.ts +0 -329
- package/src/tools/__tests__/SubagentTool.test.ts +0 -149
- package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +0 -595
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +0 -1191
- package/src/tools/__tests__/ToolNode.invalidToolCalls.test.ts +0 -757
- package/src/tools/__tests__/ToolNode.langfuse.test.ts +0 -57
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +0 -736
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +0 -1710
- package/src/tools/__tests__/ToolNode.runtimeState.test.ts +0 -120
- package/src/tools/__tests__/ToolNode.session.test.ts +0 -1352
- package/src/tools/__tests__/ToolNode.streamLimits.test.ts +0 -69
- package/src/tools/__tests__/ToolSearch.integration.test.ts +0 -161
- package/src/tools/__tests__/ToolSearch.test.ts +0 -1227
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +0 -561
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +0 -980
- package/src/tools/__tests__/directToolHooks.test.ts +0 -700
- package/src/tools/__tests__/eagerEventExecution.session.test.ts +0 -92
- package/src/tools/__tests__/handlers.test.ts +0 -997
- package/src/tools/__tests__/hitl.test.ts +0 -4663
- package/src/tools/__tests__/intentArg.test.ts +0 -357
- package/src/tools/__tests__/intentCoverage.test.ts +0 -139
- package/src/tools/__tests__/localToolNames.test.ts +0 -75
- package/src/tools/__tests__/skillCatalog.test.ts +0 -161
- package/src/tools/__tests__/subagentHooks.test.ts +0 -1651
- package/src/tools/__tests__/toolOutputReferences.test.ts +0 -515
- package/src/tools/__tests__/workspaceSeam.test.ts +0 -147
- package/src/tools/local/__tests__/FileCheckpointer.test.ts +0 -162
- package/src/tools/local/__tests__/editStrategies.test.ts +0 -134
- package/src/tools/search/content.test.ts +0 -173
- package/src/tools/search/crw.test.ts +0 -836
- package/src/tools/search/format.test.ts +0 -242
- package/src/tools/search/http-agent.test.ts +0 -133
- package/src/tools/search/jina-reranker.test.ts +0 -306
- package/src/tools/search/keenable-scraper.test.ts +0 -153
- package/src/tools/search/keenable.test.ts +0 -183
- package/src/tools/search/outcome.test.ts +0 -90
- package/src/tools/search/output.md +0 -2775
- package/src/tools/search/rag-api-reranker.test.ts +0 -891
- package/src/tools/search/source-processing.test.ts +0 -516
- package/src/tools/search/tavily.test.ts +0 -965
- package/src/tools/search/test.html +0 -884
- package/src/tools/search/test.md +0 -643
- package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +0 -637
- package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +0 -3054
- package/src/tools/subagent/__tests__/childGraphConfig.test.ts +0 -521
- package/src/utils/__tests__/apportion.test.ts +0 -32
- package/src/utils/__tests__/errors.test.ts +0 -270
- package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +0 -336
- package/src/utils/__tests__/redactSecrets.test.ts +0 -56
- package/src/utils/__tests__/truncation.test.ts +0 -66
- package/src/utils/llm.test.ts +0 -18
- package/src/utils/proxy.test.ts +0 -176
- package/src/utils/toolContent.test.ts +0 -1432
|
@@ -1,1996 +0,0 @@
|
|
|
1
|
-
import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
|
|
2
|
-
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
|
-
import type { BaseMessage } from '@langchain/core/messages';
|
|
4
|
-
import type * as t from '@/types';
|
|
5
|
-
import {
|
|
6
|
-
createSummarizeNode,
|
|
7
|
-
DEFAULT_SUMMARIZATION_PROMPT,
|
|
8
|
-
DEFAULT_UPDATE_SUMMARIZATION_PROMPT,
|
|
9
|
-
} from '@/summarization/node';
|
|
10
|
-
import { StreamLimitExceededError } from '@/llm/streamLimits';
|
|
11
|
-
import { convertInjectedMessages } from '@/messages/injected';
|
|
12
|
-
import { Constants, GraphEvents, Providers } from '@/common';
|
|
13
|
-
import { AgentContext } from '@/agents/AgentContext';
|
|
14
|
-
import * as providers from '@/llm/providers';
|
|
15
|
-
import * as eventUtils from '@/utils/events';
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Helpers
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
/** Creates a real AgentContext via fromConfig with sensible defaults.
|
|
22
|
-
* Extra properties are assigned directly for test-specific overrides.
|
|
23
|
-
*
|
|
24
|
-
* Defaults `retainRecent.turns` to `0` so that tests which use 1–2 message
|
|
25
|
-
* states still exercise the LLM-call summarization path. The recency-window
|
|
26
|
-
* default of `2` turns would otherwise short-circuit summarization for those
|
|
27
|
-
* inputs. Tests that target recency-window behavior should pass an explicit
|
|
28
|
-
* `summarizationConfig.retainRecent` value. */
|
|
29
|
-
function createAgentContext(
|
|
30
|
-
overrides: Record<string, unknown> = {}
|
|
31
|
-
): AgentContext {
|
|
32
|
-
const {
|
|
33
|
-
// AgentInputs fields
|
|
34
|
-
agentId = 'agent_0',
|
|
35
|
-
provider = Providers.OPENAI,
|
|
36
|
-
instructions = 'Test agent',
|
|
37
|
-
summarizationEnabled = true,
|
|
38
|
-
summarizationConfig,
|
|
39
|
-
maxContextTokens,
|
|
40
|
-
tools,
|
|
41
|
-
...extra
|
|
42
|
-
} = overrides;
|
|
43
|
-
|
|
44
|
-
const effectiveSummarizationConfig =
|
|
45
|
-
summarizationConfig != null
|
|
46
|
-
? summarizationConfig
|
|
47
|
-
: { retainRecent: { turns: 0 } };
|
|
48
|
-
|
|
49
|
-
const ctx = AgentContext.fromConfig({
|
|
50
|
-
agentId: agentId as string,
|
|
51
|
-
provider: provider as Providers,
|
|
52
|
-
instructions: instructions as string,
|
|
53
|
-
summarizationEnabled: summarizationEnabled as boolean,
|
|
54
|
-
summarizationConfig: effectiveSummarizationConfig,
|
|
55
|
-
...(maxContextTokens != null ? { maxContextTokens } : {}),
|
|
56
|
-
...(tools != null ? { tools } : {}),
|
|
57
|
-
} as import('@/types').AgentInputs);
|
|
58
|
-
|
|
59
|
-
// Apply direct property overrides for test-specific internal state
|
|
60
|
-
for (const [key, value] of Object.entries(extra)) {
|
|
61
|
-
(ctx as unknown as Record<string, unknown>)[key] = value;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return ctx;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Creates a mock graph container for createSummarizeNode. */
|
|
68
|
-
function mockGraph(
|
|
69
|
-
onStepCompleted?: (stepId: string, result: t.StepCompleted) => void
|
|
70
|
-
): {
|
|
71
|
-
contentData: t.RunStep[];
|
|
72
|
-
contentIndexMap: Map<string, number>;
|
|
73
|
-
config: RunnableConfig;
|
|
74
|
-
runId: string;
|
|
75
|
-
isMultiAgent: boolean;
|
|
76
|
-
dispatchRunStep: (
|
|
77
|
-
runStep: t.RunStep,
|
|
78
|
-
config?: RunnableConfig
|
|
79
|
-
) => Promise<void>;
|
|
80
|
-
dispatchRunStepCompleted: (
|
|
81
|
-
stepId: string,
|
|
82
|
-
result: t.StepCompleted,
|
|
83
|
-
config?: RunnableConfig
|
|
84
|
-
) => Promise<void>;
|
|
85
|
-
} {
|
|
86
|
-
const contentData: t.RunStep[] = [];
|
|
87
|
-
const contentIndexMap = new Map<string, number>();
|
|
88
|
-
return {
|
|
89
|
-
contentData,
|
|
90
|
-
contentIndexMap,
|
|
91
|
-
config: {} as RunnableConfig,
|
|
92
|
-
runId: 'run_1',
|
|
93
|
-
isMultiAgent: false,
|
|
94
|
-
dispatchRunStep: async (runStep: t.RunStep): Promise<void> => {
|
|
95
|
-
contentData.push(runStep);
|
|
96
|
-
contentIndexMap.set(runStep.id, runStep.index);
|
|
97
|
-
},
|
|
98
|
-
dispatchRunStepCompleted: async (
|
|
99
|
-
stepId: string,
|
|
100
|
-
result: t.StepCompleted
|
|
101
|
-
): Promise<void> => {
|
|
102
|
-
onStepCompleted?.(stepId, result);
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let stepCounter = 0;
|
|
108
|
-
function generateStepId(_stepKey: string): [string, number] {
|
|
109
|
-
const id = `step_test_${stepCounter++}`;
|
|
110
|
-
return [id, 0];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Collects custom events dispatched during the node execution. */
|
|
114
|
-
function captureEvents(): Array<{ event: string; data: unknown }> {
|
|
115
|
-
const events: Array<{ event: string; data: unknown }> = [];
|
|
116
|
-
jest.spyOn(eventUtils, 'safeDispatchCustomEvent').mockImplementation((async (
|
|
117
|
-
...args: unknown[]
|
|
118
|
-
) => {
|
|
119
|
-
events.push({ event: args[0] as string, data: args[1] });
|
|
120
|
-
}) as never);
|
|
121
|
-
return events;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Creates a mock model that returns a canned response via invoke(). */
|
|
125
|
-
function mockInvokeModel(response: string): { invoke: jest.Mock } {
|
|
126
|
-
return {
|
|
127
|
-
invoke: jest.fn().mockResolvedValue({ content: response }),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Creates a mock model that streams text chunk-by-chunk.
|
|
133
|
-
* invoke() returns the full text; stream() yields one chunk per word.
|
|
134
|
-
*/
|
|
135
|
-
function mockStreamingModel(response: string): {
|
|
136
|
-
invoke: jest.Mock;
|
|
137
|
-
stream: jest.Mock;
|
|
138
|
-
} {
|
|
139
|
-
const words = response.split(' ');
|
|
140
|
-
return {
|
|
141
|
-
invoke: jest.fn().mockResolvedValue({ content: response }),
|
|
142
|
-
stream: jest.fn().mockImplementation(async () => {
|
|
143
|
-
return (async function* (): AsyncGenerator<{ content: string }> {
|
|
144
|
-
for (const word of words) {
|
|
145
|
-
// Add space back except for first word
|
|
146
|
-
yield { content: word + ' ' };
|
|
147
|
-
}
|
|
148
|
-
})();
|
|
149
|
-
}),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
// Tests
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
|
|
157
|
-
beforeEach(() => {
|
|
158
|
-
stepCounter = 0;
|
|
159
|
-
jest.restoreAllMocks();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('createSummarizeNode', () => {
|
|
163
|
-
it('emits ON_SUMMARIZE_START and ON_SUMMARIZE_COMPLETE on success', async () => {
|
|
164
|
-
const events = captureEvents();
|
|
165
|
-
|
|
166
|
-
// Mock getChatModelClass to return our mock model
|
|
167
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
168
|
-
class {
|
|
169
|
-
constructor() {
|
|
170
|
-
return mockInvokeModel('Test summary output');
|
|
171
|
-
}
|
|
172
|
-
} as never
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
const agentContext = createAgentContext();
|
|
176
|
-
const graph = mockGraph((_stepId, result) => {
|
|
177
|
-
if (result.type === 'summary') {
|
|
178
|
-
events.push({
|
|
179
|
-
event: GraphEvents.ON_SUMMARIZE_COMPLETE,
|
|
180
|
-
data: { summary: result.summary },
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
const node = createSummarizeNode({
|
|
185
|
-
agentContext,
|
|
186
|
-
graph,
|
|
187
|
-
generateStepId,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
await node(
|
|
191
|
-
{
|
|
192
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
193
|
-
summarizationRequest: {
|
|
194
|
-
remainingContextTokens: 1000,
|
|
195
|
-
agentId: 'agent_0',
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
{} as RunnableConfig
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const eventNames = events.map((e) => e.event);
|
|
202
|
-
// ON_RUN_STEP now goes through graph.dispatchRunStep, not safeDispatchCustomEvent
|
|
203
|
-
expect(graph.contentData.length).toBeGreaterThan(0);
|
|
204
|
-
expect(eventNames).toContain(GraphEvents.ON_SUMMARIZE_START);
|
|
205
|
-
expect(eventNames).toContain(GraphEvents.ON_SUMMARIZE_COMPLETE);
|
|
206
|
-
|
|
207
|
-
// Complete event should have the summary text
|
|
208
|
-
const completeEvent = events.find(
|
|
209
|
-
(e) => e.event === GraphEvents.ON_SUMMARIZE_COMPLETE
|
|
210
|
-
);
|
|
211
|
-
expect(
|
|
212
|
-
(
|
|
213
|
-
(completeEvent?.data as t.SummarizeCompleteEvent).summary!
|
|
214
|
-
.content?.[0] as { text: string } | undefined
|
|
215
|
-
)?.text
|
|
216
|
-
).toBe('Test summary output');
|
|
217
|
-
expect(
|
|
218
|
-
(completeEvent?.data as t.SummarizeCompleteEvent).error
|
|
219
|
-
).toBeUndefined();
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('stamps INVOKED_MODEL/INVOKED_PROVIDER metadata for a dedicated summarizer model', async () => {
|
|
223
|
-
captureEvents();
|
|
224
|
-
|
|
225
|
-
const capturedConfigs: unknown[] = [];
|
|
226
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
227
|
-
class {
|
|
228
|
-
constructor() {
|
|
229
|
-
return {
|
|
230
|
-
invoke: jest
|
|
231
|
-
.fn()
|
|
232
|
-
.mockImplementation(
|
|
233
|
-
async (_messages: unknown, config?: unknown) => {
|
|
234
|
-
capturedConfigs.push(config);
|
|
235
|
-
return { content: 'Summary text' };
|
|
236
|
-
}
|
|
237
|
-
),
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
} as never
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
const agentContext = createAgentContext({
|
|
244
|
-
summarizationConfig: {
|
|
245
|
-
retainRecent: { turns: 0 },
|
|
246
|
-
model: 'gpt-4.1-mini',
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
const graph = mockGraph();
|
|
250
|
-
const node = createSummarizeNode({
|
|
251
|
-
agentContext,
|
|
252
|
-
graph,
|
|
253
|
-
generateStepId,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
await node(
|
|
257
|
-
{
|
|
258
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
259
|
-
summarizationRequest: {
|
|
260
|
-
remainingContextTokens: 1000,
|
|
261
|
-
agentId: 'agent_0',
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
{} as RunnableConfig
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Usage consumers (the subagent usage-capture handler) attribute the
|
|
269
|
-
* call from these keys — without them, a summarizer model that differs
|
|
270
|
-
* from the agent's primary would be billed against the primary config.
|
|
271
|
-
*/
|
|
272
|
-
const config = capturedConfigs[0] as {
|
|
273
|
-
metadata?: Record<string, unknown>;
|
|
274
|
-
};
|
|
275
|
-
expect(config.metadata?.[Constants.INVOKED_MODEL]).toBe('gpt-4.1-mini');
|
|
276
|
-
expect(config.metadata?.[Constants.INVOKED_PROVIDER]).toBe(
|
|
277
|
-
Providers.OPENAI
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('collects streamed text when model supports stream()', async () => {
|
|
282
|
-
captureEvents();
|
|
283
|
-
|
|
284
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
285
|
-
class {
|
|
286
|
-
constructor() {
|
|
287
|
-
return mockStreamingModel('one two three');
|
|
288
|
-
}
|
|
289
|
-
} as never
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
const setSummary = jest.fn();
|
|
293
|
-
const agentContext = createAgentContext({ setSummary } as never);
|
|
294
|
-
const graph = mockGraph();
|
|
295
|
-
const node = createSummarizeNode({
|
|
296
|
-
agentContext,
|
|
297
|
-
graph,
|
|
298
|
-
generateStepId,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
await node(
|
|
302
|
-
{
|
|
303
|
-
messages: [new HumanMessage('Test message')],
|
|
304
|
-
summarizationRequest: {
|
|
305
|
-
remainingContextTokens: 1000,
|
|
306
|
-
agentId: 'agent_0',
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
{} as RunnableConfig
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
// Node collects the full streamed text and calls setSummary.
|
|
313
|
-
// Delta events are dispatched by ChatModelStreamHandler, not the node.
|
|
314
|
-
expect(setSummary).toHaveBeenCalledWith(
|
|
315
|
-
'one two three',
|
|
316
|
-
expect.any(Number)
|
|
317
|
-
);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it('falls back to invoke when model has no stream()', async () => {
|
|
321
|
-
captureEvents();
|
|
322
|
-
|
|
323
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
324
|
-
class {
|
|
325
|
-
constructor() {
|
|
326
|
-
return mockInvokeModel('Full summary text');
|
|
327
|
-
}
|
|
328
|
-
} as never
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
const setSummary = jest.fn();
|
|
332
|
-
const agentContext = createAgentContext({ setSummary } as never);
|
|
333
|
-
const graph = mockGraph();
|
|
334
|
-
const node = createSummarizeNode({
|
|
335
|
-
agentContext,
|
|
336
|
-
graph,
|
|
337
|
-
generateStepId,
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
await node(
|
|
341
|
-
{
|
|
342
|
-
messages: [new HumanMessage('Test message')],
|
|
343
|
-
summarizationRequest: {
|
|
344
|
-
remainingContextTokens: 1000,
|
|
345
|
-
agentId: 'agent_0',
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
{} as RunnableConfig
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
// Falls back to invoke and still collects the text
|
|
352
|
-
expect(setSummary).toHaveBeenCalledWith(
|
|
353
|
-
'Full summary text',
|
|
354
|
-
expect.any(Number)
|
|
355
|
-
);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it('produces metadata stub when all LLM attempts fail', async () => {
|
|
359
|
-
const events = captureEvents();
|
|
360
|
-
|
|
361
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
362
|
-
class {
|
|
363
|
-
constructor() {
|
|
364
|
-
return {
|
|
365
|
-
invoke: jest.fn().mockRejectedValue(new Error('Model error')),
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
} as never
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
const setSummary = jest.fn();
|
|
372
|
-
const agentContext = createAgentContext({ setSummary } as never);
|
|
373
|
-
const graph = mockGraph((_stepId, result) => {
|
|
374
|
-
if (result.type === 'summary') {
|
|
375
|
-
events.push({
|
|
376
|
-
event: GraphEvents.ON_SUMMARIZE_COMPLETE,
|
|
377
|
-
data: { summary: result.summary },
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
const node = createSummarizeNode({
|
|
382
|
-
agentContext,
|
|
383
|
-
graph,
|
|
384
|
-
generateStepId,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
const result = await node(
|
|
388
|
-
{
|
|
389
|
-
messages: [new HumanMessage('Test')],
|
|
390
|
-
summarizationRequest: {
|
|
391
|
-
remainingContextTokens: 1000,
|
|
392
|
-
agentId: 'agent_0',
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
{} as RunnableConfig
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
expect(result.summarizationRequest).toBeUndefined();
|
|
399
|
-
// After summarization, REMOVE_ALL + surviving context is returned
|
|
400
|
-
expect(result.messages).toBeDefined();
|
|
401
|
-
expect(result.messages!.length).toBeGreaterThanOrEqual(1);
|
|
402
|
-
expect(result.messages![0]._getType()).toBe('remove');
|
|
403
|
-
|
|
404
|
-
// Tier 3 fallback: metadata stub is used as summary text
|
|
405
|
-
const completeEvent = events.find(
|
|
406
|
-
(e) => e.event === GraphEvents.ON_SUMMARIZE_COMPLETE
|
|
407
|
-
);
|
|
408
|
-
expect(
|
|
409
|
-
(
|
|
410
|
-
(completeEvent?.data as t.SummarizeCompleteEvent).summary!
|
|
411
|
-
.content?.[0] as { text: string } | undefined
|
|
412
|
-
)?.text
|
|
413
|
-
).toMatch(/^\[Metadata summary:/);
|
|
414
|
-
expect(
|
|
415
|
-
(completeEvent?.data as t.SummarizeCompleteEvent).error
|
|
416
|
-
).toBeUndefined();
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it('catches model initialization errors and falls back to metadata stub', async () => {
|
|
420
|
-
captureEvents();
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Simulate the "Unsupported LLM provider" case — e.g. when a caller
|
|
424
|
-
* forwards an unrecognized provider name (custom-endpoint label) that
|
|
425
|
-
* getChatModelClass cannot resolve. Prior to the defense-in-depth fix,
|
|
426
|
-
* this error was thrown outside the try/catch in executeSummarizationWithFallback
|
|
427
|
-
* and bubbled up silently. Now it is caught and the metadata stub is used.
|
|
428
|
-
*/
|
|
429
|
-
jest.spyOn(providers, 'getChatModelClass').mockImplementation(() => {
|
|
430
|
-
throw new Error('Unsupported LLM provider: Ollama');
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
const setSummary = jest.fn();
|
|
434
|
-
const agentContext = createAgentContext({ setSummary } as never);
|
|
435
|
-
const graph = mockGraph();
|
|
436
|
-
const node = createSummarizeNode({
|
|
437
|
-
agentContext,
|
|
438
|
-
graph,
|
|
439
|
-
generateStepId,
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
await expect(
|
|
443
|
-
node(
|
|
444
|
-
{
|
|
445
|
-
messages: [new HumanMessage('Test message')],
|
|
446
|
-
summarizationRequest: {
|
|
447
|
-
remainingContextTokens: 1000,
|
|
448
|
-
agentId: 'agent_0',
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
{} as RunnableConfig
|
|
452
|
-
)
|
|
453
|
-
).resolves.not.toThrow();
|
|
454
|
-
|
|
455
|
-
expect(setSummary).toHaveBeenCalledWith(
|
|
456
|
-
expect.stringContaining('[Metadata summary:'),
|
|
457
|
-
expect.any(Number)
|
|
458
|
-
);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it('falls back to metadata stub when primary LLM call fails', async () => {
|
|
462
|
-
captureEvents();
|
|
463
|
-
|
|
464
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
465
|
-
class {
|
|
466
|
-
constructor() {
|
|
467
|
-
return {
|
|
468
|
-
invoke: jest.fn().mockRejectedValue(new Error('LLM unavailable')),
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
} as never
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
const setSummary = jest.fn();
|
|
475
|
-
const agentContext = createAgentContext({ setSummary } as never);
|
|
476
|
-
const graph = mockGraph();
|
|
477
|
-
const node = createSummarizeNode({
|
|
478
|
-
agentContext,
|
|
479
|
-
graph,
|
|
480
|
-
generateStepId,
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
await node(
|
|
484
|
-
{
|
|
485
|
-
messages: [new HumanMessage('Test message')],
|
|
486
|
-
summarizationRequest: {
|
|
487
|
-
remainingContextTokens: 1000,
|
|
488
|
-
agentId: 'agent_0',
|
|
489
|
-
},
|
|
490
|
-
},
|
|
491
|
-
{} as RunnableConfig
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
expect(setSummary).toHaveBeenCalledWith(
|
|
495
|
-
expect.stringContaining('[Metadata summary:'),
|
|
496
|
-
expect.any(Number)
|
|
497
|
-
);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
it('calls setSummary with the final text', async () => {
|
|
501
|
-
captureEvents();
|
|
502
|
-
|
|
503
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
504
|
-
class {
|
|
505
|
-
constructor() {
|
|
506
|
-
return mockInvokeModel('Final summary');
|
|
507
|
-
}
|
|
508
|
-
} as never
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
const setSummary = jest.fn();
|
|
512
|
-
const agentContext = createAgentContext({ setSummary } as never);
|
|
513
|
-
const graph = mockGraph();
|
|
514
|
-
const node = createSummarizeNode({
|
|
515
|
-
agentContext,
|
|
516
|
-
graph,
|
|
517
|
-
generateStepId,
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
await node(
|
|
521
|
-
{
|
|
522
|
-
messages: [new HumanMessage('Test')],
|
|
523
|
-
summarizationRequest: {
|
|
524
|
-
remainingContextTokens: 1000,
|
|
525
|
-
agentId: 'agent_0',
|
|
526
|
-
},
|
|
527
|
-
},
|
|
528
|
-
{} as RunnableConfig
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
expect(setSummary).toHaveBeenCalledWith(
|
|
532
|
-
'Final summary',
|
|
533
|
-
expect.any(Number)
|
|
534
|
-
);
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
it('cache-hit path sends raw messages with instruction appended as final HumanMessage', async () => {
|
|
538
|
-
captureEvents();
|
|
539
|
-
|
|
540
|
-
const capturedMessages: Array<{ type: string; content: string }> = [];
|
|
541
|
-
|
|
542
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
543
|
-
class {
|
|
544
|
-
constructor() {
|
|
545
|
-
return {
|
|
546
|
-
invoke: jest
|
|
547
|
-
.fn()
|
|
548
|
-
.mockImplementation(async (messages: unknown[]) => {
|
|
549
|
-
for (const msg of messages as {
|
|
550
|
-
getType: () => string;
|
|
551
|
-
content: string | unknown[];
|
|
552
|
-
}[]) {
|
|
553
|
-
capturedMessages.push({
|
|
554
|
-
type: msg.getType(),
|
|
555
|
-
content:
|
|
556
|
-
typeof msg.content === 'string'
|
|
557
|
-
? msg.content
|
|
558
|
-
: JSON.stringify(msg.content),
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
return {
|
|
562
|
-
content:
|
|
563
|
-
'## Goal\nTest goal\n\n<events>\n<event key="test" turn="0">value</event>\n</events>',
|
|
564
|
-
};
|
|
565
|
-
}),
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
} as never
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
const agentContext = createAgentContext();
|
|
572
|
-
const graph = mockGraph();
|
|
573
|
-
const node = createSummarizeNode({
|
|
574
|
-
agentContext,
|
|
575
|
-
graph: graph as never,
|
|
576
|
-
generateStepId,
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
await node(
|
|
580
|
-
{
|
|
581
|
-
messages: [
|
|
582
|
-
new HumanMessage('Message 1'),
|
|
583
|
-
new HumanMessage('Message 2'),
|
|
584
|
-
new HumanMessage('Message 3'),
|
|
585
|
-
],
|
|
586
|
-
summarizationRequest: {
|
|
587
|
-
remainingContextTokens: 1000,
|
|
588
|
-
agentId: 'agent_0',
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
{} as RunnableConfig
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
// The raw messages should be sent + instruction appended as the last HumanMessage
|
|
595
|
-
// messagesToRefine has 3 HumanMessages, instruction adds 1 more
|
|
596
|
-
expect(capturedMessages.length).toBe(4);
|
|
597
|
-
expect(capturedMessages[0].type).toBe('human');
|
|
598
|
-
expect(capturedMessages[0].content).toBe('Message 1');
|
|
599
|
-
expect(capturedMessages[3].type).toBe('human');
|
|
600
|
-
// The last message should contain the summarization prompt
|
|
601
|
-
expect(capturedMessages[3].content).toContain(
|
|
602
|
-
'context window is filling up'
|
|
603
|
-
);
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
it('cache-hit path includes prior summary in the instruction message', async () => {
|
|
607
|
-
captureEvents();
|
|
608
|
-
|
|
609
|
-
const capturedMessages: Array<{ type: string; content: string }> = [];
|
|
610
|
-
|
|
611
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
612
|
-
class {
|
|
613
|
-
constructor() {
|
|
614
|
-
return {
|
|
615
|
-
invoke: jest
|
|
616
|
-
.fn()
|
|
617
|
-
.mockImplementation(async (messages: unknown[]) => {
|
|
618
|
-
for (const msg of messages as {
|
|
619
|
-
getType: () => string;
|
|
620
|
-
content: string | unknown[];
|
|
621
|
-
}[]) {
|
|
622
|
-
capturedMessages.push({
|
|
623
|
-
type: msg.getType(),
|
|
624
|
-
content:
|
|
625
|
-
typeof msg.content === 'string'
|
|
626
|
-
? msg.content
|
|
627
|
-
: JSON.stringify(msg.content),
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
return { content: '## Goal\nUpdated summary' };
|
|
631
|
-
}),
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
} as never
|
|
635
|
-
);
|
|
636
|
-
|
|
637
|
-
// Create context with a prior summary
|
|
638
|
-
const agentContext = createAgentContext();
|
|
639
|
-
agentContext.setSummary('## Goal\nPrior summary content.', 50);
|
|
640
|
-
|
|
641
|
-
const graph = mockGraph();
|
|
642
|
-
const node = createSummarizeNode({
|
|
643
|
-
agentContext,
|
|
644
|
-
graph: graph as never,
|
|
645
|
-
generateStepId,
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
await node(
|
|
649
|
-
{
|
|
650
|
-
messages: [new HumanMessage('New message')],
|
|
651
|
-
summarizationRequest: {
|
|
652
|
-
remainingContextTokens: 1000,
|
|
653
|
-
agentId: 'agent_0',
|
|
654
|
-
},
|
|
655
|
-
},
|
|
656
|
-
{} as RunnableConfig
|
|
657
|
-
);
|
|
658
|
-
|
|
659
|
-
// The last message should contain the update prompt (prior summary exists)
|
|
660
|
-
const lastMsg = capturedMessages[capturedMessages.length - 1];
|
|
661
|
-
expect(lastMsg.type).toBe('human');
|
|
662
|
-
expect(lastMsg.content).toContain('Merge the new messages');
|
|
663
|
-
// Should include the prior summary
|
|
664
|
-
expect(lastMsg.content).toContain('<previous-summary>');
|
|
665
|
-
expect(lastMsg.content).toContain('Prior summary content');
|
|
666
|
-
});
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
describe('DEFAULT_SUMMARIZATION_PROMPT', () => {
|
|
670
|
-
it('is exported and non-empty', () => {
|
|
671
|
-
expect(typeof DEFAULT_SUMMARIZATION_PROMPT).toBe('string');
|
|
672
|
-
expect(DEFAULT_SUMMARIZATION_PROMPT.length).toBeGreaterThan(0);
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
it('contains structured checkpoint sections', () => {
|
|
676
|
-
expect(DEFAULT_SUMMARIZATION_PROMPT).toContain('## Goal');
|
|
677
|
-
expect(DEFAULT_SUMMARIZATION_PROMPT).toContain('## Progress');
|
|
678
|
-
expect(DEFAULT_SUMMARIZATION_PROMPT).toContain('## Key Decisions');
|
|
679
|
-
expect(DEFAULT_SUMMARIZATION_PROMPT).toContain('## Next Steps');
|
|
680
|
-
});
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
describe('DEFAULT_UPDATE_SUMMARIZATION_PROMPT', () => {
|
|
684
|
-
it('is exported and non-empty', () => {
|
|
685
|
-
expect(typeof DEFAULT_UPDATE_SUMMARIZATION_PROMPT).toBe('string');
|
|
686
|
-
expect(DEFAULT_UPDATE_SUMMARIZATION_PROMPT.length).toBeGreaterThan(0);
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
it('instructs merging new content', () => {
|
|
690
|
-
expect(DEFAULT_UPDATE_SUMMARIZATION_PROMPT).toMatch(
|
|
691
|
-
/Merge the new messages/i
|
|
692
|
-
);
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
it('instructs updating progress tracking', () => {
|
|
696
|
-
expect(DEFAULT_UPDATE_SUMMARIZATION_PROMPT).toMatch(/Done/);
|
|
697
|
-
expect(DEFAULT_UPDATE_SUMMARIZATION_PROMPT).toMatch(/In Progress/);
|
|
698
|
-
});
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
describe('budget check — instructions exceed context', () => {
|
|
702
|
-
it('skips summarization when instructionTokens >= maxContextTokens', async () => {
|
|
703
|
-
const events = captureEvents();
|
|
704
|
-
const agentContext = createAgentContext({
|
|
705
|
-
maxContextTokens: 4000,
|
|
706
|
-
systemMessageTokens: 5000,
|
|
707
|
-
formatTokenBudgetBreakdown: () => 'mock breakdown',
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
const graph = mockGraph();
|
|
711
|
-
const summarizeNode = createSummarizeNode({
|
|
712
|
-
agentContext,
|
|
713
|
-
graph: graph as never,
|
|
714
|
-
generateStepId,
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
const result = await summarizeNode(
|
|
718
|
-
{
|
|
719
|
-
messages: [new HumanMessage('test')],
|
|
720
|
-
summarizationRequest: {
|
|
721
|
-
remainingContextTokens: -1000,
|
|
722
|
-
agentId: 'agent_0',
|
|
723
|
-
},
|
|
724
|
-
},
|
|
725
|
-
{} as RunnableConfig
|
|
726
|
-
);
|
|
727
|
-
|
|
728
|
-
expect(result.summarizationRequest).toBeUndefined();
|
|
729
|
-
expect(result.messages).toBeUndefined();
|
|
730
|
-
|
|
731
|
-
// No summarization events should have fired
|
|
732
|
-
const summarizeEvents = events.filter(
|
|
733
|
-
(e) =>
|
|
734
|
-
e.event === GraphEvents.ON_SUMMARIZE_START ||
|
|
735
|
-
e.event === GraphEvents.ON_SUMMARIZE_DELTA ||
|
|
736
|
-
e.event === GraphEvents.ON_SUMMARIZE_COMPLETE
|
|
737
|
-
);
|
|
738
|
-
expect(summarizeEvents).toHaveLength(0);
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
it('proceeds normally when instructionTokens < maxContextTokens', async () => {
|
|
742
|
-
captureEvents();
|
|
743
|
-
|
|
744
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
745
|
-
class {
|
|
746
|
-
constructor() {
|
|
747
|
-
return mockInvokeModel('Budget is fine summary');
|
|
748
|
-
}
|
|
749
|
-
} as never
|
|
750
|
-
);
|
|
751
|
-
|
|
752
|
-
const agentContext = createAgentContext({
|
|
753
|
-
maxContextTokens: 8000,
|
|
754
|
-
systemMessageTokens: 2000,
|
|
755
|
-
formatTokenBudgetBreakdown: () => 'mock breakdown',
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
const graph = mockGraph();
|
|
759
|
-
const summarizeNode = createSummarizeNode({
|
|
760
|
-
agentContext,
|
|
761
|
-
graph: graph as never,
|
|
762
|
-
generateStepId,
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
const result = await summarizeNode(
|
|
766
|
-
{
|
|
767
|
-
messages: [new HumanMessage('hello')],
|
|
768
|
-
summarizationRequest: {
|
|
769
|
-
remainingContextTokens: 500,
|
|
770
|
-
agentId: 'agent_0',
|
|
771
|
-
},
|
|
772
|
-
},
|
|
773
|
-
{} as RunnableConfig
|
|
774
|
-
);
|
|
775
|
-
|
|
776
|
-
// Should have summarized — messages returned for state replacement
|
|
777
|
-
expect(result.messages).toBeDefined();
|
|
778
|
-
expect(result.messages!.length).toBeGreaterThan(0);
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
describe('recency window — first-turn protection', () => {
|
|
783
|
-
it('skips the LLM call when only one turn exists (default turns: 2)', async () => {
|
|
784
|
-
const events = captureEvents();
|
|
785
|
-
|
|
786
|
-
const invokeMock = jest
|
|
787
|
-
.fn()
|
|
788
|
-
.mockResolvedValue({ content: 'should not be called' });
|
|
789
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
790
|
-
class {
|
|
791
|
-
constructor() {
|
|
792
|
-
return { invoke: invokeMock };
|
|
793
|
-
}
|
|
794
|
-
} as never
|
|
795
|
-
);
|
|
796
|
-
|
|
797
|
-
const setSummary = jest.fn();
|
|
798
|
-
const agentContext = createAgentContext({
|
|
799
|
-
summarizationConfig: {} /* defaults to retainRecent.turns = 2 */,
|
|
800
|
-
setSummary,
|
|
801
|
-
} as never);
|
|
802
|
-
const graph = mockGraph();
|
|
803
|
-
const summarizeNode = createSummarizeNode({
|
|
804
|
-
agentContext,
|
|
805
|
-
graph: graph as never,
|
|
806
|
-
generateStepId,
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
const largePayload = 'paste'.repeat(10_000);
|
|
810
|
-
const result = await summarizeNode(
|
|
811
|
-
{
|
|
812
|
-
messages: [new HumanMessage(largePayload)],
|
|
813
|
-
summarizationRequest: {
|
|
814
|
-
remainingContextTokens: 0,
|
|
815
|
-
agentId: 'agent_0',
|
|
816
|
-
},
|
|
817
|
-
},
|
|
818
|
-
{} as RunnableConfig
|
|
819
|
-
);
|
|
820
|
-
|
|
821
|
-
// No LLM call — first user message is preserved verbatim.
|
|
822
|
-
expect(invokeMock).not.toHaveBeenCalled();
|
|
823
|
-
expect(setSummary).not.toHaveBeenCalled();
|
|
824
|
-
// No state mutation — original messages stay.
|
|
825
|
-
expect(result.messages).toBeUndefined();
|
|
826
|
-
expect(result.summarizationRequest).toBeUndefined();
|
|
827
|
-
// No ON_SUMMARIZE_START emitted on the skip path.
|
|
828
|
-
const eventNames = events.map((e) => e.event);
|
|
829
|
-
expect(eventNames).not.toContain(GraphEvents.ON_SUMMARIZE_START);
|
|
830
|
-
expect(eventNames).not.toContain(GraphEvents.ON_SUMMARIZE_COMPLETE);
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
it('skips when a single-turn includes assistant + tool messages', async () => {
|
|
834
|
-
captureEvents();
|
|
835
|
-
|
|
836
|
-
const invokeMock = jest.fn().mockResolvedValue({ content: 'unused' });
|
|
837
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
838
|
-
class {
|
|
839
|
-
constructor() {
|
|
840
|
-
return { invoke: invokeMock };
|
|
841
|
-
}
|
|
842
|
-
} as never
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
const setSummary = jest.fn();
|
|
846
|
-
const agentContext = createAgentContext({
|
|
847
|
-
summarizationConfig: {},
|
|
848
|
-
setSummary,
|
|
849
|
-
} as never);
|
|
850
|
-
const graph = mockGraph();
|
|
851
|
-
const summarizeNode = createSummarizeNode({
|
|
852
|
-
agentContext,
|
|
853
|
-
graph: graph as never,
|
|
854
|
-
generateStepId,
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
const result = await summarizeNode(
|
|
858
|
-
{
|
|
859
|
-
messages: [
|
|
860
|
-
new HumanMessage('the first user prompt'),
|
|
861
|
-
new AIMessage({
|
|
862
|
-
content: '',
|
|
863
|
-
tool_calls: [{ id: 'c', name: 'search', args: {} }],
|
|
864
|
-
}),
|
|
865
|
-
new ToolMessage({
|
|
866
|
-
content: 'result',
|
|
867
|
-
tool_call_id: 'c',
|
|
868
|
-
name: 'search',
|
|
869
|
-
}),
|
|
870
|
-
new AIMessage('here is what i found'),
|
|
871
|
-
],
|
|
872
|
-
summarizationRequest: {
|
|
873
|
-
remainingContextTokens: 0,
|
|
874
|
-
agentId: 'agent_0',
|
|
875
|
-
},
|
|
876
|
-
},
|
|
877
|
-
{} as RunnableConfig
|
|
878
|
-
);
|
|
879
|
-
|
|
880
|
-
expect(invokeMock).not.toHaveBeenCalled();
|
|
881
|
-
expect(setSummary).not.toHaveBeenCalled();
|
|
882
|
-
expect(result.messages).toBeUndefined();
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
it('still summarizes the head when older turns exist beyond the recency window', async () => {
|
|
886
|
-
captureEvents();
|
|
887
|
-
|
|
888
|
-
let capturedMessages: { type: string; content: unknown }[] = [];
|
|
889
|
-
const invokeMock = jest.fn().mockImplementation((messages: unknown) => {
|
|
890
|
-
capturedMessages = (
|
|
891
|
-
messages as Array<{ getType: () => string; content: unknown }>
|
|
892
|
-
).map((m) => ({ type: m.getType(), content: m.content }));
|
|
893
|
-
return Promise.resolve({ content: 'Summary of older turns' });
|
|
894
|
-
});
|
|
895
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
896
|
-
class {
|
|
897
|
-
constructor() {
|
|
898
|
-
return { invoke: invokeMock };
|
|
899
|
-
}
|
|
900
|
-
} as never
|
|
901
|
-
);
|
|
902
|
-
|
|
903
|
-
const setSummary = jest.fn();
|
|
904
|
-
const agentContext = createAgentContext({
|
|
905
|
-
summarizationConfig: { retainRecent: { turns: 1 } },
|
|
906
|
-
setSummary,
|
|
907
|
-
} as never);
|
|
908
|
-
const graph = mockGraph();
|
|
909
|
-
const summarizeNode = createSummarizeNode({
|
|
910
|
-
agentContext,
|
|
911
|
-
graph: graph as never,
|
|
912
|
-
generateStepId,
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
const messages = [
|
|
916
|
-
new HumanMessage('turn 1 query'),
|
|
917
|
-
new AIMessage('turn 1 reply'),
|
|
918
|
-
new HumanMessage('turn 2 query'),
|
|
919
|
-
new AIMessage('turn 2 reply'),
|
|
920
|
-
];
|
|
921
|
-
const result = await summarizeNode(
|
|
922
|
-
{
|
|
923
|
-
messages,
|
|
924
|
-
summarizationRequest: {
|
|
925
|
-
remainingContextTokens: 0,
|
|
926
|
-
agentId: 'agent_0',
|
|
927
|
-
},
|
|
928
|
-
},
|
|
929
|
-
{} as RunnableConfig
|
|
930
|
-
);
|
|
931
|
-
|
|
932
|
-
// Head (turn 1) summarized; tail (turn 2) preserved verbatim.
|
|
933
|
-
expect(setSummary).toHaveBeenCalledWith(
|
|
934
|
-
expect.stringContaining('Summary of older turns'),
|
|
935
|
-
expect.any(Number)
|
|
936
|
-
);
|
|
937
|
-
// Captured messages are the head + the appended summarization instruction.
|
|
938
|
-
// Head has 2 messages (turn 1) + 1 instruction = 3 total.
|
|
939
|
-
expect(capturedMessages).toHaveLength(3);
|
|
940
|
-
expect(capturedMessages[0]?.content).toBe('turn 1 query');
|
|
941
|
-
expect(capturedMessages[1]?.content).toBe('turn 1 reply');
|
|
942
|
-
|
|
943
|
-
// Returned messages: removeAll marker + tail.
|
|
944
|
-
expect(result.messages).toBeDefined();
|
|
945
|
-
expect(result.messages![0]?._getType()).toBe('remove');
|
|
946
|
-
expect(result.messages!.slice(1)).toHaveLength(2);
|
|
947
|
-
expect((result.messages![1] as HumanMessage).content).toBe('turn 2 query');
|
|
948
|
-
expect((result.messages![2] as AIMessage).content).toBe('turn 2 reply');
|
|
949
|
-
});
|
|
950
|
-
|
|
951
|
-
describe('summary coverage', () => {
|
|
952
|
-
const runCompaction = async (
|
|
953
|
-
messages: BaseMessage[],
|
|
954
|
-
turns = 1
|
|
955
|
-
): Promise<t.SummaryContentBlock | undefined> => {
|
|
956
|
-
captureEvents();
|
|
957
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
958
|
-
class {
|
|
959
|
-
constructor() {
|
|
960
|
-
return mockInvokeModel('Summary of older turns');
|
|
961
|
-
}
|
|
962
|
-
} as never
|
|
963
|
-
);
|
|
964
|
-
|
|
965
|
-
let summaryBlock: t.SummaryContentBlock | undefined;
|
|
966
|
-
const graph = mockGraph((_stepId, result) => {
|
|
967
|
-
if (result.type === 'summary') {
|
|
968
|
-
summaryBlock = result.summary;
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
const summarizeNode = createSummarizeNode({
|
|
972
|
-
agentContext: createAgentContext({
|
|
973
|
-
summarizationConfig: { retainRecent: { turns } },
|
|
974
|
-
} as never),
|
|
975
|
-
graph: graph as never,
|
|
976
|
-
generateStepId,
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
await summarizeNode(
|
|
980
|
-
{
|
|
981
|
-
messages,
|
|
982
|
-
summarizationRequest: {
|
|
983
|
-
remainingContextTokens: 0,
|
|
984
|
-
agentId: 'agent_0',
|
|
985
|
-
},
|
|
986
|
-
},
|
|
987
|
-
{} as RunnableConfig
|
|
988
|
-
);
|
|
989
|
-
|
|
990
|
-
return summaryBlock;
|
|
991
|
-
};
|
|
992
|
-
|
|
993
|
-
it('records the first retained message as the coverage anchor', async () => {
|
|
994
|
-
const summaryBlock = await runCompaction([
|
|
995
|
-
new HumanMessage({ content: 'turn 1 query', id: 'm1' }),
|
|
996
|
-
new AIMessage({ content: 'turn 1 reply', id: 'm2' }),
|
|
997
|
-
new HumanMessage({ content: 'turn 2 query', id: 'm3' }),
|
|
998
|
-
new AIMessage({ content: 'turn 2 reply', id: 'm4' }),
|
|
999
|
-
]);
|
|
1000
|
-
|
|
1001
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm3' });
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
it('skips a retained message that carries no source id', async () => {
|
|
1005
|
-
const summaryBlock = await runCompaction([
|
|
1006
|
-
new HumanMessage({ content: 'turn 1 query', id: 'm1' }),
|
|
1007
|
-
new AIMessage({ content: 'turn 1 reply', id: 'm2' }),
|
|
1008
|
-
new HumanMessage({ content: 'turn 2 query' }),
|
|
1009
|
-
new AIMessage({ content: 'turn 2 reply', id: 'm4' }),
|
|
1010
|
-
]);
|
|
1011
|
-
|
|
1012
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm4' });
|
|
1013
|
-
});
|
|
1014
|
-
|
|
1015
|
-
it('omits coverage when no retained message carries a source id', async () => {
|
|
1016
|
-
const summaryBlock = await runCompaction([
|
|
1017
|
-
new HumanMessage('turn 1 query'),
|
|
1018
|
-
new AIMessage('turn 1 reply'),
|
|
1019
|
-
new HumanMessage('turn 2 query'),
|
|
1020
|
-
new AIMessage('turn 2 reply'),
|
|
1021
|
-
]);
|
|
1022
|
-
|
|
1023
|
-
expect(summaryBlock?.coverage).toBeUndefined();
|
|
1024
|
-
});
|
|
1025
|
-
|
|
1026
|
-
/** A steer expands one source message into pre-steer, steer, and post-steer
|
|
1027
|
-
* messages sharing its ID, and the recency split lands on the steer. The
|
|
1028
|
-
* straddling message is the anchor, so it survives whole. */
|
|
1029
|
-
it('anchors on a source id that straddles the recency boundary', async () => {
|
|
1030
|
-
const summaryBlock = await runCompaction([
|
|
1031
|
-
new HumanMessage({ content: 'turn 1 query', id: 'm1' }),
|
|
1032
|
-
new AIMessage({ content: 'pre-steer reply', id: 'm2' }),
|
|
1033
|
-
new HumanMessage({
|
|
1034
|
-
content: 'steer',
|
|
1035
|
-
id: 'm2',
|
|
1036
|
-
additional_kwargs: { role: 'user', source: 'steer' },
|
|
1037
|
-
}),
|
|
1038
|
-
new AIMessage({ content: 'post-steer reply', id: 'm2' }),
|
|
1039
|
-
]);
|
|
1040
|
-
|
|
1041
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm2' });
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
|
-
/** A steer carries `source: 'steer'` but is replayed from a payload entry
|
|
1045
|
-
* and stamped with its ID, so it is a valid anchor. When compaction lands
|
|
1046
|
-
* before any post-steer message exists it is the *only* retained entry —
|
|
1047
|
-
* treating every marked message as synthetic drops it. */
|
|
1048
|
-
it('anchors on a retained steer with no post-steer message', async () => {
|
|
1049
|
-
const summaryBlock = await runCompaction([
|
|
1050
|
-
new HumanMessage({ content: 'turn 1 query', id: 'm1' }),
|
|
1051
|
-
new AIMessage({ content: 'pre-steer reply', id: 'm2' }),
|
|
1052
|
-
new HumanMessage({
|
|
1053
|
-
content: 'steer',
|
|
1054
|
-
id: 'm2',
|
|
1055
|
-
additional_kwargs: { role: 'user', source: 'steer' },
|
|
1056
|
-
}),
|
|
1057
|
-
]);
|
|
1058
|
-
|
|
1059
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm2' });
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
it('anchors a derived retained message on its persisted source id', async () => {
|
|
1063
|
-
const summaryBlock = await runCompaction([
|
|
1064
|
-
new HumanMessage({ content: 'turn 1 query', id: 'm1' }),
|
|
1065
|
-
new AIMessage({ content: 'pre-steer reply', id: 'm2' }),
|
|
1066
|
-
new HumanMessage({
|
|
1067
|
-
content: 'steer',
|
|
1068
|
-
id: 'reducer-uuid',
|
|
1069
|
-
additional_kwargs: {
|
|
1070
|
-
role: 'user',
|
|
1071
|
-
source: 'steer',
|
|
1072
|
-
sourceMessageId: 'm2',
|
|
1073
|
-
},
|
|
1074
|
-
}),
|
|
1075
|
-
]);
|
|
1076
|
-
|
|
1077
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm2' });
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
|
-
/** `formatAgentMessages` reconstructs skill bodies inside its payload loop
|
|
1081
|
-
* and keeps processing payload entries after, so this unstamped entry — a
|
|
1082
|
-
* reducer UUID by the time compaction sees it — precedes stamped messages.
|
|
1083
|
-
* Anchoring on it would resolve to nothing on the next run. */
|
|
1084
|
-
it('skips a reconstructed skill body to reach the stamped message behind it', async () => {
|
|
1085
|
-
const summaryBlock = await runCompaction(
|
|
1086
|
-
[
|
|
1087
|
-
new HumanMessage({ content: 'turn 1 query', id: 'm1' }),
|
|
1088
|
-
new AIMessage({ content: 'turn 1 reply', id: 'm2' }),
|
|
1089
|
-
new HumanMessage({
|
|
1090
|
-
content: 'skill body',
|
|
1091
|
-
id: 'reducer-uuid',
|
|
1092
|
-
additional_kwargs: {
|
|
1093
|
-
role: 'user',
|
|
1094
|
-
isMeta: true,
|
|
1095
|
-
source: 'skill',
|
|
1096
|
-
skillName: 'demo',
|
|
1097
|
-
},
|
|
1098
|
-
}),
|
|
1099
|
-
new HumanMessage({ content: 'turn 2 query', id: 'm3' }),
|
|
1100
|
-
new AIMessage({ content: 'turn 2 reply', id: 'm4' }),
|
|
1101
|
-
],
|
|
1102
|
-
2
|
|
1103
|
-
);
|
|
1104
|
-
|
|
1105
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm3' });
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
/** `InjectedMessage` leaves both `isMeta` and `source` optional, so a bare
|
|
1109
|
-
* injected turn carries no marker of its own — and an injected `steer` is
|
|
1110
|
-
* otherwise indistinguishable from a replayed one. `convertInjectedMessages`
|
|
1111
|
-
* records `injected` on everything it builds, which decides both. */
|
|
1112
|
-
it.each([
|
|
1113
|
-
['a bare injected turn', { role: 'user' as const, content: 'injected' }],
|
|
1114
|
-
[
|
|
1115
|
-
'an injected steer',
|
|
1116
|
-
{
|
|
1117
|
-
role: 'user' as const,
|
|
1118
|
-
content: 'injected steer',
|
|
1119
|
-
source: 'steer' as const,
|
|
1120
|
-
},
|
|
1121
|
-
],
|
|
1122
|
-
])('skips %s when anchoring', async (_label, injected) => {
|
|
1123
|
-
const [converted] = convertInjectedMessages([injected]);
|
|
1124
|
-
converted.id = 'reducer-uuid';
|
|
1125
|
-
|
|
1126
|
-
const summaryBlock = await runCompaction(
|
|
1127
|
-
[
|
|
1128
|
-
new HumanMessage({ content: 'turn 1 query', id: 'm1' }),
|
|
1129
|
-
new AIMessage({ content: 'turn 1 reply', id: 'm2' }),
|
|
1130
|
-
converted,
|
|
1131
|
-
new HumanMessage({ content: 'turn 2 query', id: 'm3' }),
|
|
1132
|
-
new AIMessage({ content: 'turn 2 reply', id: 'm4' }),
|
|
1133
|
-
],
|
|
1134
|
-
2
|
|
1135
|
-
);
|
|
1136
|
-
|
|
1137
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm3' });
|
|
1138
|
-
});
|
|
1139
|
-
|
|
1140
|
-
it('anchors on the straddling id when it is the only source', async () => {
|
|
1141
|
-
const summaryBlock = await runCompaction([
|
|
1142
|
-
new AIMessage({ content: 'pre-steer reply', id: 'm1' }),
|
|
1143
|
-
new HumanMessage({
|
|
1144
|
-
content: 'steer',
|
|
1145
|
-
id: 'm1',
|
|
1146
|
-
additional_kwargs: { role: 'user', source: 'steer' },
|
|
1147
|
-
}),
|
|
1148
|
-
new AIMessage({ content: 'post-steer reply', id: 'm1' }),
|
|
1149
|
-
]);
|
|
1150
|
-
|
|
1151
|
-
expect(summaryBlock?.coverage).toEqual({ retainedFromMessageId: 'm1' });
|
|
1152
|
-
});
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1155
|
-
it('keeps the masked tail content (does not re-inject restored tool payloads into state)', async () => {
|
|
1156
|
-
captureEvents();
|
|
1157
|
-
|
|
1158
|
-
let summarizerSawRestored = false;
|
|
1159
|
-
const invokeMock = jest.fn().mockImplementation((messages: unknown) => {
|
|
1160
|
-
const arr = messages as Array<{
|
|
1161
|
-
getType: () => string;
|
|
1162
|
-
content: unknown;
|
|
1163
|
-
tool_call_id?: string;
|
|
1164
|
-
}>;
|
|
1165
|
-
// Confirm the summarizer's input for the restored tool result has
|
|
1166
|
-
// the FULL content, not the masked stub.
|
|
1167
|
-
summarizerSawRestored = arr.some(
|
|
1168
|
-
(m) =>
|
|
1169
|
-
m.getType() === 'tool' &&
|
|
1170
|
-
typeof m.content === 'string' &&
|
|
1171
|
-
(m.content as string).includes('FULL_ORIGINAL_OUTPUT')
|
|
1172
|
-
);
|
|
1173
|
-
return Promise.resolve({ content: 'summary' });
|
|
1174
|
-
});
|
|
1175
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1176
|
-
class {
|
|
1177
|
-
constructor() {
|
|
1178
|
-
return { invoke: invokeMock };
|
|
1179
|
-
}
|
|
1180
|
-
} as never
|
|
1181
|
-
);
|
|
1182
|
-
|
|
1183
|
-
const setSummary = jest.fn();
|
|
1184
|
-
const agentContext = createAgentContext({
|
|
1185
|
-
summarizationConfig: { retainRecent: { turns: 1 } },
|
|
1186
|
-
setSummary,
|
|
1187
|
-
} as never);
|
|
1188
|
-
// Restoration map keyed by message index — applies to head (idx 2)
|
|
1189
|
-
// AND to a tool message inside the retained tail (idx 5). Only the
|
|
1190
|
-
// head's restoration should leak into the summarizer; the tail must
|
|
1191
|
-
// keep the masked content.
|
|
1192
|
-
agentContext.pendingOriginalToolContent = new Map<number, string>([
|
|
1193
|
-
[2, 'FULL_ORIGINAL_OUTPUT for head tool result'],
|
|
1194
|
-
[5, 'FULL_ORIGINAL_OUTPUT for tail tool result — must NOT survive'],
|
|
1195
|
-
]);
|
|
1196
|
-
|
|
1197
|
-
const graph = mockGraph();
|
|
1198
|
-
const summarizeNode = createSummarizeNode({
|
|
1199
|
-
agentContext,
|
|
1200
|
-
graph: graph as never,
|
|
1201
|
-
generateStepId,
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
const headToolCall = new AIMessage({
|
|
1205
|
-
content: '',
|
|
1206
|
-
tool_calls: [{ id: 'h', name: 'search', args: {} }],
|
|
1207
|
-
});
|
|
1208
|
-
const headToolResult = new ToolMessage({
|
|
1209
|
-
content: 'masked-head-stub',
|
|
1210
|
-
tool_call_id: 'h',
|
|
1211
|
-
name: 'search',
|
|
1212
|
-
});
|
|
1213
|
-
const tailToolCall = new AIMessage({
|
|
1214
|
-
content: '',
|
|
1215
|
-
tool_calls: [{ id: 't', name: 'search', args: {} }],
|
|
1216
|
-
});
|
|
1217
|
-
const tailToolResult = new ToolMessage({
|
|
1218
|
-
content: 'masked-tail-stub',
|
|
1219
|
-
tool_call_id: 't',
|
|
1220
|
-
name: 'search',
|
|
1221
|
-
});
|
|
1222
|
-
const messages = [
|
|
1223
|
-
new HumanMessage('turn 1 query'),
|
|
1224
|
-
headToolCall,
|
|
1225
|
-
headToolResult, // index 2 — restored for summarizer
|
|
1226
|
-
new HumanMessage('turn 2 query'),
|
|
1227
|
-
tailToolCall,
|
|
1228
|
-
tailToolResult, // index 5 — must stay masked in returned tail
|
|
1229
|
-
];
|
|
1230
|
-
|
|
1231
|
-
const result = await summarizeNode(
|
|
1232
|
-
{
|
|
1233
|
-
messages,
|
|
1234
|
-
summarizationRequest: {
|
|
1235
|
-
remainingContextTokens: 0,
|
|
1236
|
-
agentId: 'agent_0',
|
|
1237
|
-
},
|
|
1238
|
-
},
|
|
1239
|
-
{} as RunnableConfig
|
|
1240
|
-
);
|
|
1241
|
-
|
|
1242
|
-
// Summarizer saw the full restored head tool result.
|
|
1243
|
-
expect(summarizerSawRestored).toBe(true);
|
|
1244
|
-
|
|
1245
|
-
// Returned tail must contain the MASKED tool result, not the restored one.
|
|
1246
|
-
expect(result.messages).toBeDefined();
|
|
1247
|
-
const tailToolMsg = result.messages!.find(
|
|
1248
|
-
(m) => m._getType() === 'tool'
|
|
1249
|
-
) as ToolMessage | undefined;
|
|
1250
|
-
expect(tailToolMsg).toBeDefined();
|
|
1251
|
-
expect(tailToolMsg!.content).toBe('masked-tail-stub');
|
|
1252
|
-
expect(tailToolMsg!.content).not.toContain('FULL_ORIGINAL_OUTPUT');
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
it('bounds restored tool originals and preserves ToolMessage metadata', async () => {
|
|
1256
|
-
captureEvents();
|
|
1257
|
-
|
|
1258
|
-
let restoredToolMessage: ToolMessage | undefined;
|
|
1259
|
-
const invokeMock = jest.fn().mockImplementation((messages: unknown) => {
|
|
1260
|
-
restoredToolMessage = (messages as Array<unknown>).find(
|
|
1261
|
-
(message) => message instanceof ToolMessage
|
|
1262
|
-
) as ToolMessage | undefined;
|
|
1263
|
-
return Promise.resolve({ content: 'summary' });
|
|
1264
|
-
});
|
|
1265
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1266
|
-
class {
|
|
1267
|
-
constructor() {
|
|
1268
|
-
return { invoke: invokeMock };
|
|
1269
|
-
}
|
|
1270
|
-
} as never
|
|
1271
|
-
);
|
|
1272
|
-
|
|
1273
|
-
const artifact = { source: 'clickhouse', rows: 1_000 };
|
|
1274
|
-
const originalToolMessage = new ToolMessage({
|
|
1275
|
-
content: 'masked-stub',
|
|
1276
|
-
tool_call_id: 'bounded',
|
|
1277
|
-
name: 'run_select_query',
|
|
1278
|
-
status: 'success',
|
|
1279
|
-
artifact,
|
|
1280
|
-
metadata: { traceId: 'trace-1' },
|
|
1281
|
-
additional_kwargs: { retained: true },
|
|
1282
|
-
response_metadata: { requestId: 'request-1' },
|
|
1283
|
-
});
|
|
1284
|
-
const agentContext = createAgentContext({
|
|
1285
|
-
maxContextTokens: 2_000,
|
|
1286
|
-
summarizationConfig: { retainRecent: { turns: 0 } },
|
|
1287
|
-
setSummary: jest.fn(),
|
|
1288
|
-
});
|
|
1289
|
-
agentContext.pendingOriginalToolContent = new Map([
|
|
1290
|
-
[2, `[{"rows":"${'x'.repeat(20_000)}"}]`],
|
|
1291
|
-
]);
|
|
1292
|
-
|
|
1293
|
-
const summarizeNode = createSummarizeNode({
|
|
1294
|
-
agentContext,
|
|
1295
|
-
graph: mockGraph() as never,
|
|
1296
|
-
generateStepId,
|
|
1297
|
-
});
|
|
1298
|
-
await summarizeNode(
|
|
1299
|
-
{
|
|
1300
|
-
messages: [
|
|
1301
|
-
new HumanMessage('query the table'),
|
|
1302
|
-
new AIMessage({
|
|
1303
|
-
content: '',
|
|
1304
|
-
tool_calls: [{ id: 'bounded', name: 'run_select_query', args: {} }],
|
|
1305
|
-
}),
|
|
1306
|
-
originalToolMessage,
|
|
1307
|
-
],
|
|
1308
|
-
summarizationRequest: {
|
|
1309
|
-
remainingContextTokens: 0,
|
|
1310
|
-
agentId: 'agent_0',
|
|
1311
|
-
},
|
|
1312
|
-
},
|
|
1313
|
-
{} as RunnableConfig
|
|
1314
|
-
);
|
|
1315
|
-
|
|
1316
|
-
expect(restoredToolMessage).toBeDefined();
|
|
1317
|
-
expect(typeof restoredToolMessage!.content).toBe('string');
|
|
1318
|
-
expect(String(restoredToolMessage!.content).length).toBeLessThanOrEqual(
|
|
1319
|
-
2_400
|
|
1320
|
-
);
|
|
1321
|
-
expect(restoredToolMessage!.content).toContain('[truncated:');
|
|
1322
|
-
expect(restoredToolMessage!.status).toBe('success');
|
|
1323
|
-
expect(restoredToolMessage!.artifact).toBe(artifact);
|
|
1324
|
-
expect(restoredToolMessage!.metadata).toEqual({ traceId: 'trace-1' });
|
|
1325
|
-
expect(restoredToolMessage!.additional_kwargs).toEqual({ retained: true });
|
|
1326
|
-
expect(restoredToolMessage!.response_metadata).toEqual({
|
|
1327
|
-
requestId: 'request-1',
|
|
1328
|
-
});
|
|
1329
|
-
expect(originalToolMessage.content).toBe('masked-stub');
|
|
1330
|
-
});
|
|
1331
|
-
|
|
1332
|
-
it('preserves tail-relevant pendingOriginalToolContent entries (reindexed) for future summaries', async () => {
|
|
1333
|
-
captureEvents();
|
|
1334
|
-
|
|
1335
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1336
|
-
class {
|
|
1337
|
-
constructor() {
|
|
1338
|
-
return mockInvokeModel('summary');
|
|
1339
|
-
}
|
|
1340
|
-
} as never
|
|
1341
|
-
);
|
|
1342
|
-
|
|
1343
|
-
const setSummary = jest.fn();
|
|
1344
|
-
const agentContext = createAgentContext({
|
|
1345
|
-
summarizationConfig: { retainRecent: { turns: 1 } },
|
|
1346
|
-
setSummary,
|
|
1347
|
-
} as never);
|
|
1348
|
-
// Original-content map covers BOTH a head index (1) and tail indices
|
|
1349
|
-
// (3, 5). Only the tail entries should survive, reindexed to the
|
|
1350
|
-
// post-removeAll state where tail messages start at 0.
|
|
1351
|
-
agentContext.pendingOriginalToolContent = new Map<number, string>([
|
|
1352
|
-
[1, 'fullHead'], // belongs to summarized head — should be dropped
|
|
1353
|
-
[3, 'fullTailA'], // tail position 3 → reindexed to 0
|
|
1354
|
-
[5, 'fullTailB'], // tail position 5 → reindexed to 2
|
|
1355
|
-
]);
|
|
1356
|
-
|
|
1357
|
-
const graph = mockGraph();
|
|
1358
|
-
const summarizeNode = createSummarizeNode({
|
|
1359
|
-
agentContext,
|
|
1360
|
-
graph: graph as never,
|
|
1361
|
-
generateStepId,
|
|
1362
|
-
});
|
|
1363
|
-
|
|
1364
|
-
const messages = [
|
|
1365
|
-
new HumanMessage('turn 1 query'),
|
|
1366
|
-
new AIMessage('turn 1 reply'), // idx 1
|
|
1367
|
-
new HumanMessage('turn 2 query'), // idx 2 — tail starts here
|
|
1368
|
-
new ToolMessage({
|
|
1369
|
-
// idx 3
|
|
1370
|
-
content: 'masked-stub-A',
|
|
1371
|
-
tool_call_id: 'a',
|
|
1372
|
-
name: 'search',
|
|
1373
|
-
}),
|
|
1374
|
-
new AIMessage('turn 2 reply'), // idx 4
|
|
1375
|
-
new ToolMessage({
|
|
1376
|
-
// idx 5
|
|
1377
|
-
content: 'masked-stub-B',
|
|
1378
|
-
tool_call_id: 'b',
|
|
1379
|
-
name: 'search',
|
|
1380
|
-
}),
|
|
1381
|
-
];
|
|
1382
|
-
|
|
1383
|
-
await summarizeNode(
|
|
1384
|
-
{
|
|
1385
|
-
messages,
|
|
1386
|
-
summarizationRequest: {
|
|
1387
|
-
remainingContextTokens: 0,
|
|
1388
|
-
agentId: 'agent_0',
|
|
1389
|
-
},
|
|
1390
|
-
},
|
|
1391
|
-
{} as RunnableConfig
|
|
1392
|
-
);
|
|
1393
|
-
|
|
1394
|
-
// Summarize fired (we used a real summary text), so head index 1
|
|
1395
|
-
// is gone. Tail entries should remain, reindexed by subtracting
|
|
1396
|
-
// tailStartIndex (=2): 3→1, 5→3.
|
|
1397
|
-
const carryOver = agentContext.pendingOriginalToolContent;
|
|
1398
|
-
expect(carryOver).toBeDefined();
|
|
1399
|
-
expect(carryOver!.size).toBe(2);
|
|
1400
|
-
expect(carryOver!.get(1)).toBe('fullTailA');
|
|
1401
|
-
expect(carryOver!.get(3)).toBe('fullTailB');
|
|
1402
|
-
expect(carryOver!.has(0)).toBe(false);
|
|
1403
|
-
expect(carryOver!.has(5)).toBe(false);
|
|
1404
|
-
});
|
|
1405
|
-
|
|
1406
|
-
it('aligns the dedupe baseline with the surviving tail length after compaction', async () => {
|
|
1407
|
-
captureEvents();
|
|
1408
|
-
|
|
1409
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1410
|
-
class {
|
|
1411
|
-
constructor() {
|
|
1412
|
-
return mockInvokeModel('summary');
|
|
1413
|
-
}
|
|
1414
|
-
} as never
|
|
1415
|
-
);
|
|
1416
|
-
|
|
1417
|
-
const agentContext = createAgentContext({
|
|
1418
|
-
summarizationConfig: { retainRecent: { turns: 1 } },
|
|
1419
|
-
} as never);
|
|
1420
|
-
const graph = mockGraph();
|
|
1421
|
-
const summarizeNode = createSummarizeNode({
|
|
1422
|
-
agentContext,
|
|
1423
|
-
graph: graph as never,
|
|
1424
|
-
generateStepId,
|
|
1425
|
-
});
|
|
1426
|
-
|
|
1427
|
-
const messages = [
|
|
1428
|
-
new HumanMessage('turn 1 query'),
|
|
1429
|
-
new AIMessage('turn 1 reply'),
|
|
1430
|
-
new HumanMessage('turn 2 query'),
|
|
1431
|
-
new AIMessage('turn 2 reply'),
|
|
1432
|
-
];
|
|
1433
|
-
|
|
1434
|
-
await summarizeNode(
|
|
1435
|
-
{
|
|
1436
|
-
messages,
|
|
1437
|
-
summarizationRequest: {
|
|
1438
|
-
remainingContextTokens: 0,
|
|
1439
|
-
agentId: 'agent_0',
|
|
1440
|
-
},
|
|
1441
|
-
},
|
|
1442
|
-
{} as RunnableConfig
|
|
1443
|
-
);
|
|
1444
|
-
|
|
1445
|
-
// Tail = last turn = 2 messages. Baseline must equal that count
|
|
1446
|
-
// so a follow-up prune call on the unchanged tail short-circuits
|
|
1447
|
-
// via shouldSkipSummarization rather than re-triggering compaction.
|
|
1448
|
-
expect(agentContext.shouldSkipSummarization(2)).toBe(true);
|
|
1449
|
-
expect(agentContext.shouldSkipSummarization(3)).toBe(false);
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
it('does not clear pendingOriginalToolContent on the skip path (state unchanged)', async () => {
|
|
1453
|
-
captureEvents();
|
|
1454
|
-
|
|
1455
|
-
const invokeMock = jest.fn();
|
|
1456
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1457
|
-
class {
|
|
1458
|
-
constructor() {
|
|
1459
|
-
return { invoke: invokeMock };
|
|
1460
|
-
}
|
|
1461
|
-
} as never
|
|
1462
|
-
);
|
|
1463
|
-
|
|
1464
|
-
const agentContext = createAgentContext({
|
|
1465
|
-
summarizationConfig: { retainRecent: { turns: 2 } },
|
|
1466
|
-
} as never);
|
|
1467
|
-
const seededMap = new Map<number, string>([[1, 'preserved-original']]);
|
|
1468
|
-
agentContext.pendingOriginalToolContent = seededMap;
|
|
1469
|
-
|
|
1470
|
-
const graph = mockGraph();
|
|
1471
|
-
const summarizeNode = createSummarizeNode({
|
|
1472
|
-
agentContext,
|
|
1473
|
-
graph: graph as never,
|
|
1474
|
-
generateStepId,
|
|
1475
|
-
});
|
|
1476
|
-
|
|
1477
|
-
await summarizeNode(
|
|
1478
|
-
{
|
|
1479
|
-
messages: [
|
|
1480
|
-
new HumanMessage('only turn'),
|
|
1481
|
-
new ToolMessage({
|
|
1482
|
-
content: 'masked',
|
|
1483
|
-
tool_call_id: 'x',
|
|
1484
|
-
name: 'search',
|
|
1485
|
-
}),
|
|
1486
|
-
],
|
|
1487
|
-
summarizationRequest: {
|
|
1488
|
-
remainingContextTokens: 0,
|
|
1489
|
-
agentId: 'agent_0',
|
|
1490
|
-
},
|
|
1491
|
-
},
|
|
1492
|
-
{} as RunnableConfig
|
|
1493
|
-
);
|
|
1494
|
-
|
|
1495
|
-
// No LLM call (skip path); pendingOriginalToolContent must still be
|
|
1496
|
-
// available so a future summarization can restore the original.
|
|
1497
|
-
expect(invokeMock).not.toHaveBeenCalled();
|
|
1498
|
-
expect(agentContext.pendingOriginalToolContent).toBe(seededMap);
|
|
1499
|
-
expect(agentContext.pendingOriginalToolContent!.get(1)).toBe(
|
|
1500
|
-
'preserved-original'
|
|
1501
|
-
);
|
|
1502
|
-
});
|
|
1503
|
-
|
|
1504
|
-
it('preserves the legacy "remove all, summary only" shape when retainRecent.turns is 0', async () => {
|
|
1505
|
-
captureEvents();
|
|
1506
|
-
|
|
1507
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1508
|
-
class {
|
|
1509
|
-
constructor() {
|
|
1510
|
-
return mockInvokeModel('Legacy summary');
|
|
1511
|
-
}
|
|
1512
|
-
} as never
|
|
1513
|
-
);
|
|
1514
|
-
|
|
1515
|
-
const setSummary = jest.fn();
|
|
1516
|
-
const agentContext = createAgentContext({
|
|
1517
|
-
summarizationConfig: { retainRecent: { turns: 0 } },
|
|
1518
|
-
setSummary,
|
|
1519
|
-
} as never);
|
|
1520
|
-
const graph = mockGraph();
|
|
1521
|
-
const summarizeNode = createSummarizeNode({
|
|
1522
|
-
agentContext,
|
|
1523
|
-
graph: graph as never,
|
|
1524
|
-
generateStepId,
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
const result = await summarizeNode(
|
|
1528
|
-
{
|
|
1529
|
-
messages: [
|
|
1530
|
-
new HumanMessage('only message'),
|
|
1531
|
-
new AIMessage('only reply'),
|
|
1532
|
-
],
|
|
1533
|
-
summarizationRequest: {
|
|
1534
|
-
remainingContextTokens: 0,
|
|
1535
|
-
agentId: 'agent_0',
|
|
1536
|
-
},
|
|
1537
|
-
},
|
|
1538
|
-
{} as RunnableConfig
|
|
1539
|
-
);
|
|
1540
|
-
|
|
1541
|
-
expect(setSummary).toHaveBeenCalled();
|
|
1542
|
-
// Legacy: remove-all only, no tail re-injection.
|
|
1543
|
-
expect(result.messages).toHaveLength(1);
|
|
1544
|
-
expect(result.messages![0]?._getType()).toBe('remove');
|
|
1545
|
-
});
|
|
1546
|
-
});
|
|
1547
|
-
|
|
1548
|
-
describe('emoji-heavy content does not break summarization', () => {
|
|
1549
|
-
it('summarization completes without JSON errors on emoji-heavy messages', async () => {
|
|
1550
|
-
captureEvents();
|
|
1551
|
-
|
|
1552
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1553
|
-
class {
|
|
1554
|
-
constructor() {
|
|
1555
|
-
return mockInvokeModel('Summary of emoji conversation');
|
|
1556
|
-
}
|
|
1557
|
-
} as never
|
|
1558
|
-
);
|
|
1559
|
-
|
|
1560
|
-
const emojiContent = '👨💻 coding 🎉 party 🌍 world 🚀 rocket '.repeat(30);
|
|
1561
|
-
const agentContext = createAgentContext({
|
|
1562
|
-
maxContextTokens: 8000,
|
|
1563
|
-
systemMessageTokens: 100,
|
|
1564
|
-
formatTokenBudgetBreakdown: () => 'mock breakdown',
|
|
1565
|
-
});
|
|
1566
|
-
|
|
1567
|
-
const graph = mockGraph();
|
|
1568
|
-
const summarizeNode = createSummarizeNode({
|
|
1569
|
-
agentContext,
|
|
1570
|
-
graph: graph as never,
|
|
1571
|
-
generateStepId,
|
|
1572
|
-
});
|
|
1573
|
-
|
|
1574
|
-
const result = await summarizeNode(
|
|
1575
|
-
{
|
|
1576
|
-
messages: [new HumanMessage(emojiContent)],
|
|
1577
|
-
summarizationRequest: {
|
|
1578
|
-
remainingContextTokens: 500,
|
|
1579
|
-
agentId: 'agent_0',
|
|
1580
|
-
},
|
|
1581
|
-
},
|
|
1582
|
-
{} as RunnableConfig
|
|
1583
|
-
);
|
|
1584
|
-
|
|
1585
|
-
// Should complete without throwing JSON serialization errors
|
|
1586
|
-
expect(result.messages).toBeDefined();
|
|
1587
|
-
expect(result.messages!.length).toBeGreaterThan(0);
|
|
1588
|
-
});
|
|
1589
|
-
});
|
|
1590
|
-
|
|
1591
|
-
describe('createSummarizeNode — overflow recovery', () => {
|
|
1592
|
-
/**
|
|
1593
|
-
* The first recovery compacts deterministically: re-pruning under the
|
|
1594
|
-
* corrected budget drives the pruner's tool-output compression. Spending a
|
|
1595
|
-
* summarization call there would cost money and risk replacing message
|
|
1596
|
-
* content that compression alone can shrink.
|
|
1597
|
-
*/
|
|
1598
|
-
it('skips the model call when summarization is not yet escalated', async () => {
|
|
1599
|
-
const modelFactory = jest.spyOn(providers, 'getChatModelClass');
|
|
1600
|
-
const agentContext = createAgentContext();
|
|
1601
|
-
const graph = mockGraph(() => {});
|
|
1602
|
-
const node = createSummarizeNode({ agentContext, graph, generateStepId });
|
|
1603
|
-
|
|
1604
|
-
const result = await node(
|
|
1605
|
-
{
|
|
1606
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1607
|
-
summarizationRequest: {
|
|
1608
|
-
remainingContextTokens: 0,
|
|
1609
|
-
agentId: 'agent_0',
|
|
1610
|
-
reason: 'overflow',
|
|
1611
|
-
allowSummarization: false,
|
|
1612
|
-
},
|
|
1613
|
-
},
|
|
1614
|
-
{} as RunnableConfig
|
|
1615
|
-
);
|
|
1616
|
-
|
|
1617
|
-
expect(modelFactory).not.toHaveBeenCalled();
|
|
1618
|
-
expect(result.summarizationRequest).toBeUndefined();
|
|
1619
|
-
/** State untouched, so the agent node re-prunes and retries. */
|
|
1620
|
-
expect(result.messages).toBeUndefined();
|
|
1621
|
-
});
|
|
1622
|
-
|
|
1623
|
-
it('runs the model call once the recovery escalates', async () => {
|
|
1624
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1625
|
-
class {
|
|
1626
|
-
constructor() {
|
|
1627
|
-
return mockInvokeModel('Escalated summary');
|
|
1628
|
-
}
|
|
1629
|
-
} as never
|
|
1630
|
-
);
|
|
1631
|
-
const agentContext = createAgentContext();
|
|
1632
|
-
const graph = mockGraph(() => {});
|
|
1633
|
-
const node = createSummarizeNode({ agentContext, graph, generateStepId });
|
|
1634
|
-
|
|
1635
|
-
await node(
|
|
1636
|
-
{
|
|
1637
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1638
|
-
summarizationRequest: {
|
|
1639
|
-
remainingContextTokens: 0,
|
|
1640
|
-
agentId: 'agent_0',
|
|
1641
|
-
reason: 'overflow',
|
|
1642
|
-
allowSummarization: true,
|
|
1643
|
-
},
|
|
1644
|
-
},
|
|
1645
|
-
{} as RunnableConfig
|
|
1646
|
-
);
|
|
1647
|
-
|
|
1648
|
-
expect(agentContext.getSummaryText()).toContain('Escalated summary');
|
|
1649
|
-
});
|
|
1650
|
-
|
|
1651
|
-
/**
|
|
1652
|
-
* The metadata stub describes the history instead of summarizing it, so
|
|
1653
|
-
* committing it removes the head and keeps nothing of what it said. That is
|
|
1654
|
-
* never an acceptable way to paper over an overflow.
|
|
1655
|
-
*/
|
|
1656
|
-
it('keeps history when the escalated summarizer falls back to a stub', async () => {
|
|
1657
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1658
|
-
class {
|
|
1659
|
-
constructor() {
|
|
1660
|
-
return {
|
|
1661
|
-
invoke: (): never => {
|
|
1662
|
-
throw new Error(
|
|
1663
|
-
'400 prompt is too long: 900 tokens > 500 maximum'
|
|
1664
|
-
);
|
|
1665
|
-
},
|
|
1666
|
-
stream: (): never => {
|
|
1667
|
-
throw new Error(
|
|
1668
|
-
'400 prompt is too long: 900 tokens > 500 maximum'
|
|
1669
|
-
);
|
|
1670
|
-
},
|
|
1671
|
-
};
|
|
1672
|
-
}
|
|
1673
|
-
} as never
|
|
1674
|
-
);
|
|
1675
|
-
const agentContext = createAgentContext();
|
|
1676
|
-
const graph = mockGraph(() => {});
|
|
1677
|
-
const node = createSummarizeNode({ agentContext, graph, generateStepId });
|
|
1678
|
-
|
|
1679
|
-
const result = await node(
|
|
1680
|
-
{
|
|
1681
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1682
|
-
summarizationRequest: {
|
|
1683
|
-
remainingContextTokens: 0,
|
|
1684
|
-
agentId: 'agent_0',
|
|
1685
|
-
reason: 'overflow',
|
|
1686
|
-
allowSummarization: true,
|
|
1687
|
-
},
|
|
1688
|
-
},
|
|
1689
|
-
{} as RunnableConfig
|
|
1690
|
-
);
|
|
1691
|
-
|
|
1692
|
-
expect(agentContext.hasSummary()).toBe(false);
|
|
1693
|
-
expect(result.messages).toBeUndefined();
|
|
1694
|
-
});
|
|
1695
|
-
|
|
1696
|
-
it('still commits a stub for a configured trigger, preserving prior behavior', async () => {
|
|
1697
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1698
|
-
class {
|
|
1699
|
-
constructor() {
|
|
1700
|
-
return {
|
|
1701
|
-
invoke: (): never => {
|
|
1702
|
-
throw new Error('upstream unavailable');
|
|
1703
|
-
},
|
|
1704
|
-
stream: (): never => {
|
|
1705
|
-
throw new Error('upstream unavailable');
|
|
1706
|
-
},
|
|
1707
|
-
};
|
|
1708
|
-
}
|
|
1709
|
-
} as never
|
|
1710
|
-
);
|
|
1711
|
-
const agentContext = createAgentContext();
|
|
1712
|
-
const graph = mockGraph(() => {});
|
|
1713
|
-
const node = createSummarizeNode({ agentContext, graph, generateStepId });
|
|
1714
|
-
|
|
1715
|
-
await node(
|
|
1716
|
-
{
|
|
1717
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1718
|
-
summarizationRequest: {
|
|
1719
|
-
remainingContextTokens: 0,
|
|
1720
|
-
agentId: 'agent_0',
|
|
1721
|
-
},
|
|
1722
|
-
},
|
|
1723
|
-
{} as RunnableConfig
|
|
1724
|
-
);
|
|
1725
|
-
|
|
1726
|
-
expect(agentContext.hasSummary()).toBe(true);
|
|
1727
|
-
});
|
|
1728
|
-
});
|
|
1729
|
-
|
|
1730
|
-
describe('summarize node breaker capture', () => {
|
|
1731
|
-
it('stamps the entry-captured breaker epoch into summary attempt metadata', async () => {
|
|
1732
|
-
captureEvents();
|
|
1733
|
-
const capturedConfigs: Array<{ metadata?: Record<string, unknown> }> = [];
|
|
1734
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1735
|
-
class {
|
|
1736
|
-
constructor() {
|
|
1737
|
-
return {
|
|
1738
|
-
invoke: jest
|
|
1739
|
-
.fn()
|
|
1740
|
-
.mockImplementation(
|
|
1741
|
-
async (_messages: unknown, config?: unknown) => {
|
|
1742
|
-
capturedConfigs.push(
|
|
1743
|
-
config as { metadata?: Record<string, unknown> }
|
|
1744
|
-
);
|
|
1745
|
-
return { content: 'Summary text' };
|
|
1746
|
-
}
|
|
1747
|
-
),
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
|
-
} as never
|
|
1751
|
-
);
|
|
1752
|
-
|
|
1753
|
-
const agentContext = createAgentContext();
|
|
1754
|
-
const graph = {
|
|
1755
|
-
...mockGraph(),
|
|
1756
|
-
getBreakerEpoch: (): number => 7,
|
|
1757
|
-
};
|
|
1758
|
-
const node = createSummarizeNode({
|
|
1759
|
-
agentContext,
|
|
1760
|
-
graph,
|
|
1761
|
-
generateStepId,
|
|
1762
|
-
});
|
|
1763
|
-
|
|
1764
|
-
await node(
|
|
1765
|
-
{
|
|
1766
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1767
|
-
summarizationRequest: {
|
|
1768
|
-
remainingContextTokens: 1000,
|
|
1769
|
-
agentId: 'agent_0',
|
|
1770
|
-
},
|
|
1771
|
-
},
|
|
1772
|
-
{} as RunnableConfig
|
|
1773
|
-
);
|
|
1774
|
-
|
|
1775
|
-
expect(capturedConfigs.length).toBeGreaterThan(0);
|
|
1776
|
-
for (const config of capturedConfigs) {
|
|
1777
|
-
expect(config.metadata?.lc_stream_limit_epoch).toBe(7);
|
|
1778
|
-
}
|
|
1779
|
-
});
|
|
1780
|
-
|
|
1781
|
-
it('rejects before the model call when the breaker trips during pre-call awaits', async () => {
|
|
1782
|
-
const trip = new StreamLimitExceededError({
|
|
1783
|
-
kind: 'tool_call_args',
|
|
1784
|
-
limit: 10,
|
|
1785
|
-
observed: 11,
|
|
1786
|
-
toolName: 'db_query',
|
|
1787
|
-
});
|
|
1788
|
-
const entryBreaker = new AbortController();
|
|
1789
|
-
/** The trip lands while ON_SUMMARIZE_START is awaited — after the
|
|
1790
|
-
* entry check already passed. */
|
|
1791
|
-
jest
|
|
1792
|
-
.spyOn(eventUtils, 'safeDispatchCustomEvent')
|
|
1793
|
-
.mockImplementation((async (...args: unknown[]) => {
|
|
1794
|
-
if (args[0] === GraphEvents.ON_SUMMARIZE_START) {
|
|
1795
|
-
entryBreaker.abort(trip);
|
|
1796
|
-
}
|
|
1797
|
-
}) as never);
|
|
1798
|
-
const modelClassSpy = jest
|
|
1799
|
-
.spyOn(providers, 'getChatModelClass')
|
|
1800
|
-
.mockReturnValue(
|
|
1801
|
-
class {
|
|
1802
|
-
constructor() {
|
|
1803
|
-
return mockInvokeModel('should never run');
|
|
1804
|
-
}
|
|
1805
|
-
} as never
|
|
1806
|
-
);
|
|
1807
|
-
|
|
1808
|
-
const agentContext = createAgentContext();
|
|
1809
|
-
const graph = {
|
|
1810
|
-
...mockGraph(),
|
|
1811
|
-
getBreakerSignal: (): AbortSignal => entryBreaker.signal,
|
|
1812
|
-
};
|
|
1813
|
-
const node = createSummarizeNode({
|
|
1814
|
-
agentContext,
|
|
1815
|
-
graph,
|
|
1816
|
-
generateStepId,
|
|
1817
|
-
});
|
|
1818
|
-
|
|
1819
|
-
await expect(
|
|
1820
|
-
node(
|
|
1821
|
-
{
|
|
1822
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1823
|
-
summarizationRequest: {
|
|
1824
|
-
remainingContextTokens: 1000,
|
|
1825
|
-
agentId: 'agent_0',
|
|
1826
|
-
},
|
|
1827
|
-
},
|
|
1828
|
-
{} as RunnableConfig
|
|
1829
|
-
)
|
|
1830
|
-
).rejects.toBe(trip);
|
|
1831
|
-
expect(modelClassSpy).not.toHaveBeenCalled();
|
|
1832
|
-
});
|
|
1833
|
-
|
|
1834
|
-
it('rethrows a parent trip on the config signal instead of degrading to the stub', async () => {
|
|
1835
|
-
const trip = new StreamLimitExceededError({
|
|
1836
|
-
kind: 'tool_call_args',
|
|
1837
|
-
limit: 10,
|
|
1838
|
-
observed: 11,
|
|
1839
|
-
toolName: 'db_query',
|
|
1840
|
-
});
|
|
1841
|
-
/** Child graph's own breaker stays live: in a subagent, a ROOT
|
|
1842
|
-
* sibling's trip arrives only through the composed invocation signal. */
|
|
1843
|
-
const childBreaker = new AbortController();
|
|
1844
|
-
const configAbort = new AbortController();
|
|
1845
|
-
captureEvents();
|
|
1846
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1847
|
-
class {
|
|
1848
|
-
constructor() {
|
|
1849
|
-
return {
|
|
1850
|
-
invoke: jest.fn().mockImplementation(async () => {
|
|
1851
|
-
configAbort.abort(trip);
|
|
1852
|
-
throw new Error('The operation was aborted');
|
|
1853
|
-
}),
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1856
|
-
} as never
|
|
1857
|
-
);
|
|
1858
|
-
|
|
1859
|
-
const agentContext = createAgentContext();
|
|
1860
|
-
const graph = {
|
|
1861
|
-
...mockGraph(),
|
|
1862
|
-
getBreakerSignal: (): AbortSignal => childBreaker.signal,
|
|
1863
|
-
};
|
|
1864
|
-
const node = createSummarizeNode({
|
|
1865
|
-
agentContext,
|
|
1866
|
-
graph,
|
|
1867
|
-
generateStepId,
|
|
1868
|
-
});
|
|
1869
|
-
|
|
1870
|
-
await expect(
|
|
1871
|
-
node(
|
|
1872
|
-
{
|
|
1873
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1874
|
-
summarizationRequest: {
|
|
1875
|
-
remainingContextTokens: 1000,
|
|
1876
|
-
agentId: 'agent_0',
|
|
1877
|
-
},
|
|
1878
|
-
},
|
|
1879
|
-
{ signal: configAbort.signal } as RunnableConfig
|
|
1880
|
-
)
|
|
1881
|
-
).rejects.toBe(trip);
|
|
1882
|
-
});
|
|
1883
|
-
|
|
1884
|
-
it('rejects at entry when the breaker has already tripped', async () => {
|
|
1885
|
-
const trip = new StreamLimitExceededError({
|
|
1886
|
-
kind: 'tool_call_args',
|
|
1887
|
-
limit: 10,
|
|
1888
|
-
observed: 11,
|
|
1889
|
-
toolName: 'db_query',
|
|
1890
|
-
});
|
|
1891
|
-
const entryBreaker = new AbortController();
|
|
1892
|
-
entryBreaker.abort(trip);
|
|
1893
|
-
|
|
1894
|
-
const modelClassSpy = jest
|
|
1895
|
-
.spyOn(providers, 'getChatModelClass')
|
|
1896
|
-
.mockReturnValue(
|
|
1897
|
-
class {
|
|
1898
|
-
constructor() {
|
|
1899
|
-
return mockInvokeModel('should never run');
|
|
1900
|
-
}
|
|
1901
|
-
} as never
|
|
1902
|
-
);
|
|
1903
|
-
|
|
1904
|
-
const agentContext = createAgentContext();
|
|
1905
|
-
const graph = {
|
|
1906
|
-
...mockGraph(),
|
|
1907
|
-
getBreakerSignal: (): AbortSignal => entryBreaker.signal,
|
|
1908
|
-
};
|
|
1909
|
-
const node = createSummarizeNode({
|
|
1910
|
-
agentContext,
|
|
1911
|
-
graph,
|
|
1912
|
-
generateStepId,
|
|
1913
|
-
});
|
|
1914
|
-
|
|
1915
|
-
await expect(
|
|
1916
|
-
node(
|
|
1917
|
-
{
|
|
1918
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1919
|
-
summarizationRequest: {
|
|
1920
|
-
remainingContextTokens: 1000,
|
|
1921
|
-
agentId: 'agent_0',
|
|
1922
|
-
},
|
|
1923
|
-
},
|
|
1924
|
-
{} as RunnableConfig
|
|
1925
|
-
)
|
|
1926
|
-
).rejects.toBe(trip);
|
|
1927
|
-
|
|
1928
|
-
expect(graph.contentData).toHaveLength(0);
|
|
1929
|
-
expect(modelClassSpy).not.toHaveBeenCalled();
|
|
1930
|
-
});
|
|
1931
|
-
|
|
1932
|
-
it('binds the model call to the breaker signal read at node entry', async () => {
|
|
1933
|
-
const entryBreaker = new AbortController();
|
|
1934
|
-
const lateBreaker = new AbortController();
|
|
1935
|
-
let started = false;
|
|
1936
|
-
jest
|
|
1937
|
-
.spyOn(eventUtils, 'safeDispatchCustomEvent')
|
|
1938
|
-
.mockImplementation((async (...args: unknown[]) => {
|
|
1939
|
-
if (args[0] === GraphEvents.ON_SUMMARIZE_START) {
|
|
1940
|
-
started = true;
|
|
1941
|
-
}
|
|
1942
|
-
}) as never);
|
|
1943
|
-
|
|
1944
|
-
const capturedSignals: Array<AbortSignal | undefined> = [];
|
|
1945
|
-
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1946
|
-
class {
|
|
1947
|
-
constructor() {
|
|
1948
|
-
return {
|
|
1949
|
-
invoke: jest
|
|
1950
|
-
.fn()
|
|
1951
|
-
.mockImplementation(
|
|
1952
|
-
async (_messages: unknown, config?: unknown) => {
|
|
1953
|
-
capturedSignals.push(
|
|
1954
|
-
(config as RunnableConfig | undefined)?.signal
|
|
1955
|
-
);
|
|
1956
|
-
return { content: 'Summary text' };
|
|
1957
|
-
}
|
|
1958
|
-
),
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
} as never
|
|
1962
|
-
);
|
|
1963
|
-
|
|
1964
|
-
const agentContext = createAgentContext();
|
|
1965
|
-
/** Simulates a graph reset between node entry and the model call: once
|
|
1966
|
-
* ON_SUMMARIZE_START has been awaited, the live accessor hands out a
|
|
1967
|
-
* fresh controller's signal. The model call must still see the signal
|
|
1968
|
-
* captured at entry. */
|
|
1969
|
-
const graph = {
|
|
1970
|
-
...mockGraph(),
|
|
1971
|
-
getBreakerSignal: (): AbortSignal =>
|
|
1972
|
-
started ? lateBreaker.signal : entryBreaker.signal,
|
|
1973
|
-
};
|
|
1974
|
-
const node = createSummarizeNode({
|
|
1975
|
-
agentContext,
|
|
1976
|
-
graph,
|
|
1977
|
-
generateStepId,
|
|
1978
|
-
});
|
|
1979
|
-
|
|
1980
|
-
await node(
|
|
1981
|
-
{
|
|
1982
|
-
messages: [new HumanMessage('Hello'), new HumanMessage('World')],
|
|
1983
|
-
summarizationRequest: {
|
|
1984
|
-
remainingContextTokens: 1000,
|
|
1985
|
-
agentId: 'agent_0',
|
|
1986
|
-
},
|
|
1987
|
-
},
|
|
1988
|
-
{} as RunnableConfig
|
|
1989
|
-
);
|
|
1990
|
-
|
|
1991
|
-
expect(capturedSignals.length).toBeGreaterThan(0);
|
|
1992
|
-
for (const signal of capturedSignals) {
|
|
1993
|
-
expect(signal).toBe(entryBreaker.signal);
|
|
1994
|
-
}
|
|
1995
|
-
});
|
|
1996
|
-
});
|