@librechat/agents 3.2.2 → 3.2.31
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 +3 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +200 -54
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +13 -7
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +33 -0
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +17 -1
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +2 -2
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +1 -1
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +2 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +8 -5
- package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/fake.cjs +16 -14
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +22 -0
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +88 -27
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +2 -2
- package/dist/cjs/llm/invoke.cjs +108 -11
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +8 -7
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +124 -17
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/reducer.cjs +1 -1
- package/dist/cjs/messages/reducer.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs +1 -1
- package/dist/cjs/messages/tools.cjs.map +1 -1
- package/dist/cjs/openai/index.cjs.map +1 -1
- package/dist/cjs/responses/index.cjs.map +1 -1
- package/dist/cjs/run.cjs +47 -21
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +4 -4
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
- package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +2 -2
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +248 -25
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/Calculator.cjs +1 -1
- package/dist/cjs/tools/Calculator.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +37 -18
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +1 -1
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +7 -4
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs +4 -4
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +2 -1
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
- package/dist/cjs/tools/local/FileCheckpointer.cjs +2 -1
- package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +45 -19
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionTools.cjs +2 -2
- package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +4 -3
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/local/attachments.cjs +0 -5
- package/dist/cjs/tools/local/attachments.cjs.map +1 -1
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +4 -4
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +7 -3
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +76 -8
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs +1 -1
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/cjs/utils/run.cjs +1 -1
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +3 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +200 -54
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +13 -7
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +33 -1
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +17 -2
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +2 -2
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +1 -1
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +2 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +8 -5
- package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
- package/dist/esm/llm/fake.mjs +16 -14
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +23 -1
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +88 -27
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +2 -2
- package/dist/esm/llm/invoke.mjs +104 -7
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +1 -1
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/messages/cache.mjs +8 -7
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +124 -18
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/reducer.mjs +1 -1
- package/dist/esm/messages/reducer.mjs.map +1 -1
- package/dist/esm/messages/tools.mjs +1 -1
- package/dist/esm/messages/tools.mjs.map +1 -1
- package/dist/esm/openai/index.mjs.map +1 -1
- package/dist/esm/responses/index.mjs.map +1 -1
- package/dist/esm/run.mjs +47 -21
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -4
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +2 -2
- package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +2 -2
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/stream.mjs +248 -25
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/Calculator.mjs +1 -1
- package/dist/esm/tools/Calculator.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +1 -1
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +37 -18
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +1 -1
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +7 -4
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs +4 -4
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +2 -1
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
- package/dist/esm/tools/local/FileCheckpointer.mjs +2 -1
- package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +45 -19
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
- package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionTools.mjs +2 -2
- package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +4 -3
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/local/attachments.mjs +0 -5
- package/dist/esm/tools/local/attachments.mjs.map +1 -1
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +4 -4
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +1 -1
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +8 -4
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs +1 -1
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +76 -9
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs +1 -1
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/esm/utils/run.mjs +1 -1
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +1 -1
- package/dist/types/events.d.ts +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -1
- package/dist/types/hooks/executeHooks.d.ts +1 -1
- package/dist/types/hooks/types.d.ts +5 -0
- package/dist/types/instrumentation.d.ts +1 -0
- package/dist/types/langfuse.d.ts +4 -0
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -1
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +2 -2
- package/dist/types/llm/bedrock/index.d.ts +2 -2
- package/dist/types/llm/fake.d.ts +3 -3
- package/dist/types/llm/google/index.d.ts +1 -0
- package/dist/types/llm/google/types.d.ts +1 -1
- package/dist/types/llm/google/utils/common.d.ts +2 -2
- package/dist/types/llm/google/utils/tools.d.ts +1 -1
- package/dist/types/llm/google/utils/zod_to_genai_parameters.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +2 -2
- package/dist/types/llm/openai/utils/index.d.ts +1 -1
- package/dist/types/llm/openrouter/index.d.ts +4 -4
- package/dist/types/messages/contextPruning.d.ts +1 -1
- package/dist/types/messages/format.d.ts +9 -4
- package/dist/types/messages/prune.d.ts +1 -1
- package/dist/types/session/JsonlSessionStore.d.ts +1 -1
- package/dist/types/session/handlers.d.ts +1 -1
- package/dist/types/session/types.d.ts +1 -1
- package/dist/types/summarization/node.d.ts +1 -1
- package/dist/types/tools/SubagentTool.d.ts +2 -2
- package/dist/types/tools/ToolNode.d.ts +9 -2
- package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +1 -1
- package/dist/types/tools/search/types.d.ts +1 -1
- package/dist/types/tools/search/utils.d.ts +11 -0
- package/dist/types/types/graph.d.ts +12 -4
- package/dist/types/types/llm.d.ts +4 -3
- package/dist/types/types/messages.d.ts +1 -1
- package/dist/types/types/run.d.ts +6 -6
- package/dist/types/types/stream.d.ts +2 -2
- package/dist/types/types/tools.d.ts +5 -1
- package/dist/types/utils/handlers.d.ts +2 -2
- package/dist/types/utils/run.d.ts +1 -1
- package/package.json +6 -3
- package/src/__tests__/stream.eagerEventExecution.test.ts +543 -6
- package/src/agents/AgentContext.ts +2 -2
- package/src/agents/__tests__/AgentContext.test.ts +3 -3
- package/src/agents/__tests__/promptCacheLiveHelpers.ts +1 -1
- package/src/events.ts +1 -1
- package/src/graphs/Graph.ts +329 -72
- package/src/graphs/MultiAgentGraph.ts +1 -1
- package/src/graphs/__tests__/Graph.reasoning.test.ts +919 -6
- package/src/graphs/__tests__/MultiAgentGraph.test.ts +1 -1
- package/src/graphs/__tests__/composition.smoke.test.ts +1 -1
- package/src/hooks/__tests__/HookRegistry.test.ts +1 -1
- package/src/hooks/__tests__/compactHooks.test.ts +8 -8
- package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +34 -22
- package/src/hooks/__tests__/executeHooks.test.ts +3 -3
- package/src/hooks/__tests__/integration.test.ts +3 -3
- package/src/hooks/__tests__/toolHooks.test.ts +10 -10
- package/src/hooks/createWorkspacePolicyHook.ts +17 -14
- package/src/hooks/executeHooks.ts +1 -1
- package/src/hooks/types.ts +5 -0
- package/src/instrumentation.ts +49 -8
- package/src/langfuse.ts +35 -1
- package/src/langfuseToolOutputTracing.ts +2 -2
- package/src/llm/anthropic/index.ts +1 -1
- package/src/llm/anthropic/utils/message_inputs.ts +1 -1
- package/src/llm/anthropic/utils/message_outputs.ts +3 -5
- package/src/llm/anthropic/utils/output_parsers.ts +5 -5
- package/src/llm/bedrock/index.ts +4 -4
- package/src/llm/bedrock/toolCache.test.ts +48 -9
- package/src/llm/bedrock/toolCache.ts +11 -6
- package/src/llm/custom-chat-models.smoke.test.ts +114 -0
- package/src/llm/fake.ts +30 -25
- package/src/llm/google/index.ts +43 -1
- package/src/llm/google/llm.spec.ts +173 -1
- package/src/llm/google/types.ts +1 -1
- package/src/llm/google/utils/common.ts +154 -45
- package/src/llm/google/utils/tools.ts +8 -8
- package/src/llm/google/utils/zod_to_genai_parameters.ts +4 -4
- package/src/llm/invoke.test.ts +3 -3
- package/src/llm/invoke.ts +170 -10
- package/src/llm/openai/index.ts +4 -4
- package/src/llm/openai/utils/index.ts +14 -14
- package/src/llm/openrouter/index.ts +4 -4
- package/src/llm/openrouter/reasoning.test.ts +2 -2
- package/src/llm/vertexai/fixThoughtSignatures.test.ts +1 -1
- package/src/llm/vertexai/index.ts +1 -1
- package/src/messages/cache.test.ts +22 -0
- package/src/messages/cache.ts +25 -12
- package/src/messages/content.ts +1 -1
- package/src/messages/contextPruning.ts +1 -1
- package/src/messages/format.ts +227 -43
- package/src/messages/formatAgentMessages.skills.test.ts +105 -26
- package/src/messages/formatAgentMessages.test.ts +841 -10
- package/src/messages/labelContentByAgent.test.ts +2 -2
- package/src/messages/prune.ts +1 -1
- package/src/messages/reducer.ts +1 -1
- package/src/messages/tools.ts +1 -1
- package/src/openai/__tests__/openai.test.ts +2 -2
- package/src/openai/index.ts +1 -1
- package/src/responses/__tests__/responses.test.ts +2 -2
- package/src/responses/index.ts +1 -1
- package/src/run.ts +82 -47
- package/src/session/AgentSession.ts +6 -6
- package/src/session/JsonlSessionStore.ts +3 -3
- package/src/session/__tests__/JsonlSessionStore.test.ts +5 -5
- package/src/session/__tests__/handlers.test.ts +2 -2
- package/src/session/handlers.ts +5 -5
- package/src/session/types.ts +1 -1
- package/src/specs/agent-handoffs.test.ts +1 -1
- package/src/specs/deterministic-trace-id.test.ts +50 -0
- package/src/specs/langfuse-callbacks.test.ts +2 -2
- package/src/specs/langfuse-metadata.test.ts +39 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +1 -1
- package/src/specs/multi-agent-summarization.test.ts +4 -4
- package/src/specs/subagent.test.ts +3 -3
- package/src/specs/summarization-unit.test.ts +1 -1
- package/src/specs/thinking-handoff.test.ts +1 -1
- package/src/splitStream.test.ts +48 -0
- package/src/stream.test.ts +53 -3
- package/src/stream.ts +450 -39
- package/src/summarization/__tests__/aggregator.test.ts +2 -2
- package/src/summarization/__tests__/node.test.ts +2 -2
- package/src/summarization/node.ts +1 -1
- package/src/tools/BashProgrammaticToolCalling.ts +5 -5
- package/src/tools/Calculator.ts +1 -1
- package/src/tools/CodeExecutor.ts +2 -4
- package/src/tools/SubagentTool.ts +2 -2
- package/src/tools/ToolNode.ts +37 -16
- package/src/tools/ToolSearch.ts +1 -1
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +4 -4
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +12 -12
- package/src/tools/__tests__/LocalExecutionTools.test.ts +125 -93
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +29 -5
- package/src/tools/__tests__/ReadFile.test.ts +1 -1
- package/src/tools/__tests__/SkillTool.test.ts +4 -4
- package/src/tools/__tests__/SubagentExecutor.test.ts +17 -13
- package/src/tools/__tests__/SubagentTool.test.ts +2 -2
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +1 -1
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +2 -5
- package/src/tools/__tests__/ToolNode.session.test.ts +1 -1
- package/src/tools/__tests__/ToolSearch.test.ts +1 -1
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +1 -1
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +35 -32
- package/src/tools/__tests__/directToolHooks.test.ts +41 -0
- package/src/tools/__tests__/handlers.test.ts +2 -2
- package/src/tools/__tests__/hitl.test.ts +11 -11
- package/src/tools/__tests__/localToolNames.test.ts +8 -6
- package/src/tools/__tests__/skillCatalog.test.ts +1 -1
- package/src/tools/__tests__/subagentHooks.test.ts +20 -10
- package/src/tools/__tests__/workspaceSeam.test.ts +20 -7
- package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +9 -6
- package/src/tools/cloudflare/CloudflareSandboxTools.ts +19 -19
- package/src/tools/handlers.ts +5 -5
- package/src/tools/local/CompileCheckTool.ts +4 -7
- package/src/tools/local/FileCheckpointer.ts +6 -5
- package/src/tools/local/LocalCodingTools.ts +100 -45
- package/src/tools/local/LocalExecutionEngine.ts +5 -5
- package/src/tools/local/LocalExecutionTools.ts +9 -9
- package/src/tools/local/LocalProgrammaticToolCalling.ts +5 -4
- package/src/tools/local/attachments.ts +0 -6
- package/src/tools/local/resolveLocalExecutionTools.ts +15 -15
- package/src/tools/search/firecrawl.ts +1 -1
- package/src/tools/search/jina-reranker.test.ts +148 -37
- package/src/tools/search/rerankers.ts +14 -4
- package/src/tools/search/tavily-search.ts +2 -2
- package/src/tools/search/types.ts +1 -1
- package/src/tools/search/utils.ts +99 -9
- package/src/tools/subagent/SubagentExecutor.ts +12 -6
- package/src/types/graph.ts +20 -12
- package/src/types/llm.ts +7 -6
- package/src/types/messages.ts +1 -1
- package/src/types/run.ts +7 -7
- package/src/types/stream.ts +2 -2
- package/src/types/tools.ts +5 -1
- package/src/utils/handlers.ts +2 -2
- package/src/utils/llmConfig.ts +1 -1
- package/src/utils/logging.ts +20 -10
- package/src/utils/run.ts +2 -2
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
import { AIMessageChunk, HumanMessage } from '@langchain/core/messages';
|
|
2
1
|
import { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
3
2
|
import { FakeListChatModel } from '@langchain/core/utils/testing';
|
|
3
|
+
import { AIMessageChunk, HumanMessage } from '@langchain/core/messages';
|
|
4
4
|
import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
|
|
5
|
-
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
6
5
|
import type { BaseMessage, UsageMetadata } from '@langchain/core/messages';
|
|
6
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
7
|
+
import type { OpenAIClient } from '@langchain/openai';
|
|
7
8
|
import type * as t from '@/types';
|
|
8
|
-
import { ContentTypes, GraphEvents, Providers } from '@/common';
|
|
9
|
-
import { createContentAggregator } from '@/stream';
|
|
9
|
+
import { ContentTypes, GraphEvents, Providers, StepTypes } from '@/common';
|
|
10
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
10
11
|
import { ModelEndHandler, ToolEndHandler } from '@/events';
|
|
12
|
+
import { toLangChainContent } from '@/messages/langchain';
|
|
13
|
+
import { ChatOpenRouter } from '@/llm/openrouter';
|
|
11
14
|
import { Run } from '@/run';
|
|
12
15
|
|
|
13
16
|
type ReasoningKey = 'reasoning_content' | 'reasoning';
|
|
17
|
+
type StreamingCompletionBackedModel = {
|
|
18
|
+
completions: {
|
|
19
|
+
completionWithRetry: () => Promise<
|
|
20
|
+
AsyncIterable<OpenAIClient.Chat.Completions.ChatCompletionChunk>
|
|
21
|
+
>;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
14
24
|
|
|
15
25
|
class InvokeOnlyReasoningModel implements t.ChatModel {
|
|
16
26
|
constructor(
|
|
@@ -93,6 +103,25 @@ class CallbackStreamingReasoningModel extends FakeListChatModel {
|
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
105
|
|
|
106
|
+
function createOpenRouterStreamChunk(
|
|
107
|
+
content: string,
|
|
108
|
+
finishReason: OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice['finish_reason'] = null
|
|
109
|
+
): OpenAIClient.Chat.Completions.ChatCompletionChunk {
|
|
110
|
+
return {
|
|
111
|
+
id: 'chatcmpl-openrouter-test',
|
|
112
|
+
object: 'chat.completion.chunk',
|
|
113
|
+
created: 0,
|
|
114
|
+
model: 'google/gemini-test',
|
|
115
|
+
choices: [
|
|
116
|
+
{
|
|
117
|
+
index: 0,
|
|
118
|
+
delta: { content },
|
|
119
|
+
finish_reason: finishReason,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
96
125
|
function createReasoningChunk(
|
|
97
126
|
reasoningKey: ReasoningKey,
|
|
98
127
|
reasoningText: string
|
|
@@ -105,7 +134,9 @@ function createReasoningChunk(
|
|
|
105
134
|
});
|
|
106
135
|
}
|
|
107
136
|
|
|
108
|
-
function createOpenAIReasoningSummaryChunk(
|
|
137
|
+
function createOpenAIReasoningSummaryChunk(
|
|
138
|
+
reasoningText: string
|
|
139
|
+
): AIMessageChunk {
|
|
109
140
|
return new AIMessageChunk({
|
|
110
141
|
content: '',
|
|
111
142
|
additional_kwargs: {
|
|
@@ -123,7 +154,10 @@ function createReasoningHandlers(
|
|
|
123
154
|
): Record<string | GraphEvents, t.EventHandler> {
|
|
124
155
|
return {
|
|
125
156
|
[GraphEvents.ON_RUN_STEP]: {
|
|
126
|
-
handle: (
|
|
157
|
+
handle: (
|
|
158
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
159
|
+
data: t.StreamEventData
|
|
160
|
+
): void => {
|
|
127
161
|
aggregateContent({ event, data: data as t.RunStep });
|
|
128
162
|
},
|
|
129
163
|
},
|
|
@@ -326,6 +360,176 @@ describe('StandardGraph final response reasoning fallback', () => {
|
|
|
326
360
|
]);
|
|
327
361
|
});
|
|
328
362
|
|
|
363
|
+
it('emits final text from invoke-only Gemini server-side context responses', async () => {
|
|
364
|
+
const text = 'Search complete.';
|
|
365
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
366
|
+
const run = await Run.create<t.IState>({
|
|
367
|
+
runId: 'reasoning-fallback-gemini-server-side-context',
|
|
368
|
+
graphConfig: {
|
|
369
|
+
type: 'standard',
|
|
370
|
+
llmConfig: {
|
|
371
|
+
provider: Providers.GOOGLE,
|
|
372
|
+
disableStreaming: true,
|
|
373
|
+
streamUsage: false,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
returnContent: true,
|
|
377
|
+
skipCleanup: true,
|
|
378
|
+
customHandlers: createReasoningHandlers(aggregateContent, []),
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (!run.Graph) {
|
|
382
|
+
throw new Error('Expected graph to be initialized');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const messageContent: t.MessageContentComplex[] = [
|
|
386
|
+
{
|
|
387
|
+
type: 'toolCall',
|
|
388
|
+
toolCall: {
|
|
389
|
+
id: 'server-search-1',
|
|
390
|
+
name: 'google_search',
|
|
391
|
+
args: {},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{ type: ContentTypes.TEXT, text },
|
|
395
|
+
{
|
|
396
|
+
type: 'toolResponse',
|
|
397
|
+
toolResponse: {
|
|
398
|
+
id: 'server-search-1',
|
|
399
|
+
name: 'google_search',
|
|
400
|
+
response: { results: [] },
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
run.Graph.overrideModel = new InvokeOnlyMessageModel(
|
|
405
|
+
new AIMessageChunk({
|
|
406
|
+
content: toLangChainContent(messageContent),
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const finalContentParts = await run.processStream(
|
|
411
|
+
{ messages: [new HumanMessage('search and answer')] },
|
|
412
|
+
{
|
|
413
|
+
...config,
|
|
414
|
+
configurable: {
|
|
415
|
+
thread_id: 'reasoning-fallback-gemini-server-side-context',
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
expect(finalContentParts).toEqual(messageContent);
|
|
421
|
+
expect(contentParts).toEqual(messageContent);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('does not preserve Gemini server-side context blocks for non-Google invoke fallbacks', async () => {
|
|
425
|
+
const text = 'Search complete.';
|
|
426
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
427
|
+
const run = await Run.create<t.IState>({
|
|
428
|
+
runId: 'reasoning-fallback-non-google-server-side-shape',
|
|
429
|
+
graphConfig: {
|
|
430
|
+
type: 'standard',
|
|
431
|
+
llmConfig,
|
|
432
|
+
},
|
|
433
|
+
returnContent: true,
|
|
434
|
+
skipCleanup: true,
|
|
435
|
+
customHandlers: createReasoningHandlers(aggregateContent, []),
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
if (!run.Graph) {
|
|
439
|
+
throw new Error('Expected graph to be initialized');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const messageContent: t.MessageContentComplex[] = [
|
|
443
|
+
{
|
|
444
|
+
type: 'toolCall',
|
|
445
|
+
toolCall: {
|
|
446
|
+
id: 'server-search-1',
|
|
447
|
+
name: 'google_search',
|
|
448
|
+
args: {},
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{ type: ContentTypes.TEXT, text },
|
|
452
|
+
{
|
|
453
|
+
type: 'toolResponse',
|
|
454
|
+
toolResponse: {
|
|
455
|
+
id: 'server-search-1',
|
|
456
|
+
name: 'google_search',
|
|
457
|
+
response: { results: [] },
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
];
|
|
461
|
+
run.Graph.overrideModel = new InvokeOnlyMessageModel(
|
|
462
|
+
new AIMessageChunk({
|
|
463
|
+
content: toLangChainContent(messageContent),
|
|
464
|
+
})
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
const finalContentParts = await run.processStream(
|
|
468
|
+
{ messages: [new HumanMessage('search and answer')] },
|
|
469
|
+
{
|
|
470
|
+
...config,
|
|
471
|
+
configurable: {
|
|
472
|
+
thread_id: 'reasoning-fallback-non-google-server-side-shape',
|
|
473
|
+
},
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
expect(finalContentParts).toEqual(messageContent);
|
|
478
|
+
expect(contentParts).toEqual([]);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('uses the final fallback for one-shot disableStreaming mixed chunks', async () => {
|
|
482
|
+
const text = 'Cloudflare content check.';
|
|
483
|
+
const reasoningText = 'Plan the exact final wording.';
|
|
484
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
485
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
486
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
487
|
+
const run = await Run.create<t.IState>({
|
|
488
|
+
runId: 'reasoning-fallback-disable-streaming-mixed-final-chunk',
|
|
489
|
+
graphConfig: {
|
|
490
|
+
type: 'standard',
|
|
491
|
+
llmConfig,
|
|
492
|
+
},
|
|
493
|
+
returnContent: true,
|
|
494
|
+
skipCleanup: true,
|
|
495
|
+
customHandlers: createReasoningHandlers(
|
|
496
|
+
aggregateContent,
|
|
497
|
+
reasoningDeltas,
|
|
498
|
+
messageDeltas
|
|
499
|
+
),
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
if (!run.Graph) {
|
|
503
|
+
throw new Error('Expected graph to be initialized');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
507
|
+
new AIMessageChunk({
|
|
508
|
+
content: text,
|
|
509
|
+
additional_kwargs: {
|
|
510
|
+
reasoning_content: reasoningText,
|
|
511
|
+
},
|
|
512
|
+
}),
|
|
513
|
+
]);
|
|
514
|
+
|
|
515
|
+
await run.processStream(
|
|
516
|
+
{ messages: [new HumanMessage('return mixed final chunk')] },
|
|
517
|
+
{
|
|
518
|
+
...config,
|
|
519
|
+
configurable: {
|
|
520
|
+
thread_id: 'reasoning-fallback-disable-streaming-mixed-final-chunk',
|
|
521
|
+
},
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
expect(reasoningDeltas).toHaveLength(1);
|
|
526
|
+
expect(messageDeltas).toHaveLength(1);
|
|
527
|
+
expect(contentParts).toEqual([
|
|
528
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
529
|
+
{ type: ContentTypes.TEXT, text },
|
|
530
|
+
]);
|
|
531
|
+
});
|
|
532
|
+
|
|
329
533
|
it('returns reasoning content without a custom aggregator', async () => {
|
|
330
534
|
const reasoningText = 'Reasoning should persist for returnContent.';
|
|
331
535
|
const run = await Run.create<t.IState>({
|
|
@@ -555,6 +759,715 @@ describe('StandardGraph final response reasoning fallback', () => {
|
|
|
555
759
|
}
|
|
556
760
|
);
|
|
557
761
|
|
|
762
|
+
it('streams OpenRouter reasoning_content before visible text', async () => {
|
|
763
|
+
const text = '391.';
|
|
764
|
+
const reasoningText = 'Use the difference of squares.';
|
|
765
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
766
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
767
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
768
|
+
const run = await Run.create<t.IState>({
|
|
769
|
+
runId: 'reasoning-fallback-openrouter-reasoning-content-stream',
|
|
770
|
+
graphConfig: {
|
|
771
|
+
type: 'standard',
|
|
772
|
+
llmConfig: {
|
|
773
|
+
provider: Providers.OPENROUTER,
|
|
774
|
+
streamUsage: false,
|
|
775
|
+
},
|
|
776
|
+
reasoningKey: 'reasoning',
|
|
777
|
+
},
|
|
778
|
+
returnContent: true,
|
|
779
|
+
skipCleanup: true,
|
|
780
|
+
customHandlers: createReasoningHandlers(
|
|
781
|
+
aggregateContent,
|
|
782
|
+
reasoningDeltas,
|
|
783
|
+
messageDeltas
|
|
784
|
+
),
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
if (!run.Graph) {
|
|
788
|
+
throw new Error('Expected graph to be initialized');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
792
|
+
createReasoningChunk('reasoning_content', reasoningText),
|
|
793
|
+
new AIMessageChunk({ content: text }),
|
|
794
|
+
]);
|
|
795
|
+
|
|
796
|
+
await run.processStream(
|
|
797
|
+
{ messages: [new HumanMessage('stream OpenRouter reasoning_content')] },
|
|
798
|
+
{
|
|
799
|
+
...config,
|
|
800
|
+
configurable: {
|
|
801
|
+
thread_id: 'reasoning-fallback-openrouter-reasoning-content-stream',
|
|
802
|
+
},
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
expect(reasoningDeltas).toHaveLength(1);
|
|
807
|
+
expect(messageDeltas).toHaveLength(1);
|
|
808
|
+
expect(contentParts).toEqual([
|
|
809
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
810
|
+
{ type: ContentTypes.TEXT, text },
|
|
811
|
+
]);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it('does not replay streamed OpenRouter text when the final chunk has reasoning_details', async () => {
|
|
815
|
+
const text = '391.';
|
|
816
|
+
const reasoningText = '17 times 23 equals 391.';
|
|
817
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
818
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
819
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
820
|
+
const run = await Run.create<t.IState>({
|
|
821
|
+
runId: 'reasoning-fallback-openrouter-final-details-stream',
|
|
822
|
+
graphConfig: {
|
|
823
|
+
type: 'standard',
|
|
824
|
+
llmConfig: {
|
|
825
|
+
provider: Providers.OPENROUTER,
|
|
826
|
+
streamUsage: false,
|
|
827
|
+
},
|
|
828
|
+
reasoningKey: 'reasoning',
|
|
829
|
+
},
|
|
830
|
+
returnContent: true,
|
|
831
|
+
skipCleanup: true,
|
|
832
|
+
customHandlers: createReasoningHandlers(
|
|
833
|
+
aggregateContent,
|
|
834
|
+
reasoningDeltas,
|
|
835
|
+
messageDeltas
|
|
836
|
+
),
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
if (!run.Graph) {
|
|
840
|
+
throw new Error('Expected graph to be initialized');
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
844
|
+
new AIMessageChunk({
|
|
845
|
+
content: text,
|
|
846
|
+
additional_kwargs: {
|
|
847
|
+
reasoning_details: [{ type: 'reasoning.text', text: reasoningText }],
|
|
848
|
+
},
|
|
849
|
+
}),
|
|
850
|
+
]);
|
|
851
|
+
|
|
852
|
+
await run.processStream(
|
|
853
|
+
{ messages: [new HumanMessage('think briefly, what is 17 x 23?')] },
|
|
854
|
+
{
|
|
855
|
+
...config,
|
|
856
|
+
configurable: {
|
|
857
|
+
thread_id: 'reasoning-fallback-openrouter-final-details-stream',
|
|
858
|
+
},
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
expect(reasoningDeltas).toHaveLength(1);
|
|
863
|
+
expect(messageDeltas).toHaveLength(1);
|
|
864
|
+
expect(contentParts).toEqual([
|
|
865
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
866
|
+
{ type: ContentTypes.TEXT, text },
|
|
867
|
+
]);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it('does not keep an OpenRouter streamed text replay before final reasoning_details fallback', async () => {
|
|
871
|
+
const text = '391.';
|
|
872
|
+
const reasoningText = '17 times 23 equals 391.';
|
|
873
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
874
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
875
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
876
|
+
const run = await Run.create<t.IState>({
|
|
877
|
+
runId: 'reasoning-fallback-openrouter-streamed-text-final-details',
|
|
878
|
+
graphConfig: {
|
|
879
|
+
type: 'standard',
|
|
880
|
+
llmConfig: {
|
|
881
|
+
provider: Providers.OPENROUTER,
|
|
882
|
+
streamUsage: false,
|
|
883
|
+
},
|
|
884
|
+
reasoningKey: 'reasoning',
|
|
885
|
+
},
|
|
886
|
+
returnContent: true,
|
|
887
|
+
skipCleanup: true,
|
|
888
|
+
customHandlers: createReasoningHandlers(
|
|
889
|
+
aggregateContent,
|
|
890
|
+
reasoningDeltas,
|
|
891
|
+
messageDeltas
|
|
892
|
+
),
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
if (!run.Graph) {
|
|
896
|
+
throw new Error('Expected graph to be initialized');
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
900
|
+
new AIMessageChunk({ content: text }),
|
|
901
|
+
new AIMessageChunk({
|
|
902
|
+
content: text,
|
|
903
|
+
additional_kwargs: {
|
|
904
|
+
reasoning_details: [{ type: 'reasoning.text', text: reasoningText }],
|
|
905
|
+
},
|
|
906
|
+
}),
|
|
907
|
+
]);
|
|
908
|
+
|
|
909
|
+
const finalContentParts = await run.processStream(
|
|
910
|
+
{ messages: [new HumanMessage('think briefly, what is 17 x 23?')] },
|
|
911
|
+
{
|
|
912
|
+
...config,
|
|
913
|
+
configurable: {
|
|
914
|
+
thread_id:
|
|
915
|
+
'reasoning-fallback-openrouter-streamed-text-final-details',
|
|
916
|
+
},
|
|
917
|
+
}
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
expect(reasoningDeltas).toHaveLength(0);
|
|
921
|
+
expect(messageDeltas).toHaveLength(1);
|
|
922
|
+
expect(contentParts).toEqual([{ type: ContentTypes.TEXT, text }]);
|
|
923
|
+
expect(finalContentParts).toEqual([
|
|
924
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
925
|
+
{ type: ContentTypes.TEXT, text },
|
|
926
|
+
]);
|
|
927
|
+
expect(run.getRunMessages()?.[0]?.content).toBe(text);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
it('does not replay streamed text when late OpenRouter reasoning_content arrives', async () => {
|
|
931
|
+
const text = '391.';
|
|
932
|
+
const reasoningText = 'Confirm the arithmetic after visible text.';
|
|
933
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
934
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
935
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
936
|
+
const run = await Run.create<t.IState>({
|
|
937
|
+
runId: 'reasoning-fallback-openrouter-text-before-reasoning-content',
|
|
938
|
+
graphConfig: {
|
|
939
|
+
type: 'standard',
|
|
940
|
+
llmConfig: {
|
|
941
|
+
provider: Providers.OPENROUTER,
|
|
942
|
+
streamUsage: false,
|
|
943
|
+
},
|
|
944
|
+
reasoningKey: 'reasoning',
|
|
945
|
+
},
|
|
946
|
+
returnContent: true,
|
|
947
|
+
skipCleanup: true,
|
|
948
|
+
customHandlers: createReasoningHandlers(
|
|
949
|
+
aggregateContent,
|
|
950
|
+
reasoningDeltas,
|
|
951
|
+
messageDeltas
|
|
952
|
+
),
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
if (!run.Graph) {
|
|
956
|
+
throw new Error('Expected graph to be initialized');
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
960
|
+
new AIMessageChunk({ content: text }),
|
|
961
|
+
createReasoningChunk('reasoning_content', reasoningText),
|
|
962
|
+
]);
|
|
963
|
+
|
|
964
|
+
const finalContentParts = await run.processStream(
|
|
965
|
+
{ messages: [new HumanMessage('think briefly, what is 17 x 23?')] },
|
|
966
|
+
{
|
|
967
|
+
...config,
|
|
968
|
+
configurable: {
|
|
969
|
+
thread_id:
|
|
970
|
+
'reasoning-fallback-openrouter-text-before-reasoning-content',
|
|
971
|
+
},
|
|
972
|
+
}
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
expect(reasoningDeltas).toHaveLength(0);
|
|
976
|
+
expect(messageDeltas).toHaveLength(1);
|
|
977
|
+
expect(contentParts).toEqual([{ type: ContentTypes.TEXT, text }]);
|
|
978
|
+
expect(finalContentParts).toEqual([
|
|
979
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
980
|
+
{ type: ContentTypes.TEXT, text },
|
|
981
|
+
]);
|
|
982
|
+
expect(run.getRunMessages()?.[0]?.content).toBe(text);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
it('does not replay streamed text when the final fallback is on the reasoning key', async () => {
|
|
986
|
+
const text = '391.';
|
|
987
|
+
const reasoningText = 'Confirm the arithmetic after visible text.';
|
|
988
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
989
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
990
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
991
|
+
const run = await Run.create<t.IState>({
|
|
992
|
+
runId: 'reasoning-fallback-openrouter-text-before-reasoning-key',
|
|
993
|
+
graphConfig: {
|
|
994
|
+
type: 'standard',
|
|
995
|
+
llmConfig: {
|
|
996
|
+
provider: Providers.OPENROUTER,
|
|
997
|
+
streamUsage: false,
|
|
998
|
+
},
|
|
999
|
+
reasoningKey: 'reasoning',
|
|
1000
|
+
},
|
|
1001
|
+
returnContent: true,
|
|
1002
|
+
skipCleanup: true,
|
|
1003
|
+
customHandlers: createReasoningHandlers(
|
|
1004
|
+
aggregateContent,
|
|
1005
|
+
reasoningDeltas,
|
|
1006
|
+
messageDeltas
|
|
1007
|
+
),
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
if (!run.Graph) {
|
|
1011
|
+
throw new Error('Expected graph to be initialized');
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
1015
|
+
new AIMessageChunk({ content: text }),
|
|
1016
|
+
createReasoningChunk('reasoning', reasoningText),
|
|
1017
|
+
]);
|
|
1018
|
+
|
|
1019
|
+
const finalContentParts = await run.processStream(
|
|
1020
|
+
{ messages: [new HumanMessage('think briefly, what is 17 x 23?')] },
|
|
1021
|
+
{
|
|
1022
|
+
...config,
|
|
1023
|
+
configurable: {
|
|
1024
|
+
thread_id: 'reasoning-fallback-openrouter-text-before-reasoning-key',
|
|
1025
|
+
},
|
|
1026
|
+
}
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
expect(reasoningDeltas).toHaveLength(0);
|
|
1030
|
+
expect(messageDeltas).toHaveLength(1);
|
|
1031
|
+
expect(contentParts).toEqual([{ type: ContentTypes.TEXT, text }]);
|
|
1032
|
+
expect(finalContentParts).toEqual([
|
|
1033
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
1034
|
+
{ type: ContentTypes.TEXT, text },
|
|
1035
|
+
]);
|
|
1036
|
+
expect(run.getRunMessages()?.[0]?.content).toBe(text);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it('keeps new OpenRouter final text suffixes that carry reasoning_details', async () => {
|
|
1040
|
+
const firstText = 'Hello ';
|
|
1041
|
+
const replayWithSuffix = 'Hello world';
|
|
1042
|
+
const reasoningText = 'The greeting needs one more word.';
|
|
1043
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
1044
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
1045
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
1046
|
+
const run = await Run.create<t.IState>({
|
|
1047
|
+
runId: 'reasoning-fallback-openrouter-final-suffix-details',
|
|
1048
|
+
graphConfig: {
|
|
1049
|
+
type: 'standard',
|
|
1050
|
+
llmConfig: {
|
|
1051
|
+
provider: Providers.OPENROUTER,
|
|
1052
|
+
streamUsage: false,
|
|
1053
|
+
},
|
|
1054
|
+
reasoningKey: 'reasoning',
|
|
1055
|
+
},
|
|
1056
|
+
returnContent: true,
|
|
1057
|
+
skipCleanup: true,
|
|
1058
|
+
customHandlers: createReasoningHandlers(
|
|
1059
|
+
aggregateContent,
|
|
1060
|
+
reasoningDeltas,
|
|
1061
|
+
messageDeltas
|
|
1062
|
+
),
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
if (!run.Graph) {
|
|
1066
|
+
throw new Error('Expected graph to be initialized');
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
1070
|
+
new AIMessageChunk({ content: firstText }),
|
|
1071
|
+
new AIMessageChunk({
|
|
1072
|
+
content: replayWithSuffix,
|
|
1073
|
+
additional_kwargs: {
|
|
1074
|
+
reasoning_details: [{ type: 'reasoning.text', text: reasoningText }],
|
|
1075
|
+
},
|
|
1076
|
+
}),
|
|
1077
|
+
]);
|
|
1078
|
+
|
|
1079
|
+
const finalContentParts = await run.processStream(
|
|
1080
|
+
{ messages: [new HumanMessage('finish the greeting')] },
|
|
1081
|
+
{
|
|
1082
|
+
...config,
|
|
1083
|
+
configurable: {
|
|
1084
|
+
thread_id: 'reasoning-fallback-openrouter-final-suffix-details',
|
|
1085
|
+
},
|
|
1086
|
+
}
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
expect(reasoningDeltas).toHaveLength(0);
|
|
1090
|
+
expect(messageDeltas).toHaveLength(2);
|
|
1091
|
+
expect(messageDeltas[1].delta.content?.[0]).toEqual({
|
|
1092
|
+
type: ContentTypes.TEXT,
|
|
1093
|
+
text: 'world',
|
|
1094
|
+
});
|
|
1095
|
+
expect(contentParts).toEqual([
|
|
1096
|
+
{ type: ContentTypes.TEXT, text: replayWithSuffix },
|
|
1097
|
+
]);
|
|
1098
|
+
expect(finalContentParts).toEqual([
|
|
1099
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
1100
|
+
{ type: ContentTypes.TEXT, text: replayWithSuffix },
|
|
1101
|
+
]);
|
|
1102
|
+
expect(run.getRunMessages()?.[0]?.content).toBe(replayWithSuffix);
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
it('keeps post-tool final text when an earlier invocation streamed text', async () => {
|
|
1106
|
+
const streamedBeforeTool = 'I will check that. ';
|
|
1107
|
+
const finalAfterTool = 'The answer is 391.';
|
|
1108
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
1109
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
1110
|
+
const { aggregateContent } = createContentAggregator();
|
|
1111
|
+
const run = await Run.create<t.IState>({
|
|
1112
|
+
runId: 'reasoning-fallback-post-tool-final-text',
|
|
1113
|
+
graphConfig: {
|
|
1114
|
+
type: 'standard',
|
|
1115
|
+
llmConfig: {
|
|
1116
|
+
provider: Providers.OPENAI,
|
|
1117
|
+
disableStreaming: true,
|
|
1118
|
+
streamUsage: false,
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
returnContent: true,
|
|
1122
|
+
skipCleanup: true,
|
|
1123
|
+
customHandlers: createReasoningHandlers(
|
|
1124
|
+
aggregateContent,
|
|
1125
|
+
reasoningDeltas,
|
|
1126
|
+
messageDeltas
|
|
1127
|
+
),
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
if (!run.Graph) {
|
|
1131
|
+
throw new Error('Expected graph to be initialized');
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const graph = run.Graph;
|
|
1135
|
+
const metadata = {
|
|
1136
|
+
thread_id: 'reasoning-fallback-post-tool-final-text',
|
|
1137
|
+
langgraph_node: 'agent=default',
|
|
1138
|
+
langgraph_step: 1,
|
|
1139
|
+
checkpoint_ns: '',
|
|
1140
|
+
};
|
|
1141
|
+
const runConfig = {
|
|
1142
|
+
...config,
|
|
1143
|
+
configurable: {
|
|
1144
|
+
thread_id: 'reasoning-fallback-post-tool-final-text',
|
|
1145
|
+
},
|
|
1146
|
+
metadata,
|
|
1147
|
+
};
|
|
1148
|
+
graph.config = runConfig;
|
|
1149
|
+
const streamedStepKey = graph.getStepKey(metadata);
|
|
1150
|
+
await graph.dispatchRunStep(
|
|
1151
|
+
streamedStepKey,
|
|
1152
|
+
{
|
|
1153
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
1154
|
+
message_creation: { message_id: 'msg-before-tool' },
|
|
1155
|
+
},
|
|
1156
|
+
metadata
|
|
1157
|
+
);
|
|
1158
|
+
await graph.dispatchMessageDelta(
|
|
1159
|
+
graph.getStepIdByKey(streamedStepKey),
|
|
1160
|
+
{
|
|
1161
|
+
content: [
|
|
1162
|
+
{
|
|
1163
|
+
type: ContentTypes.TEXT,
|
|
1164
|
+
text: streamedBeforeTool,
|
|
1165
|
+
},
|
|
1166
|
+
],
|
|
1167
|
+
},
|
|
1168
|
+
metadata
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1171
|
+
graph.invokedToolIds = new Set(['call_calculator']);
|
|
1172
|
+
graph.overrideModel = new InvokeOnlyMessageModel(
|
|
1173
|
+
new AIMessageChunk({ content: finalAfterTool })
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
await graph.createCallModel('default')(
|
|
1177
|
+
{ messages: [new HumanMessage('continue after tool')] },
|
|
1178
|
+
runConfig
|
|
1179
|
+
);
|
|
1180
|
+
|
|
1181
|
+
expect(messageDeltas.map((delta) => delta.delta.content?.[0])).toEqual([
|
|
1182
|
+
{
|
|
1183
|
+
type: ContentTypes.TEXT,
|
|
1184
|
+
text: streamedBeforeTool,
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
type: ContentTypes.TEXT,
|
|
1188
|
+
text: finalAfterTool,
|
|
1189
|
+
},
|
|
1190
|
+
]);
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
it('does not replay streamed text block variants from the final fallback', async () => {
|
|
1194
|
+
const text = 'Variant text.';
|
|
1195
|
+
const textDeltaContent: t.MessageDelta['content'] = [
|
|
1196
|
+
{ type: 'text_delta', text },
|
|
1197
|
+
];
|
|
1198
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
1199
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
1200
|
+
const { aggregateContent } = createContentAggregator();
|
|
1201
|
+
const run = await Run.create<t.IState>({
|
|
1202
|
+
runId: 'reasoning-fallback-text-delta-block',
|
|
1203
|
+
graphConfig: {
|
|
1204
|
+
type: 'standard',
|
|
1205
|
+
llmConfig: {
|
|
1206
|
+
provider: Providers.OPENAI,
|
|
1207
|
+
disableStreaming: true,
|
|
1208
|
+
streamUsage: false,
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
returnContent: true,
|
|
1212
|
+
skipCleanup: true,
|
|
1213
|
+
customHandlers: createReasoningHandlers(
|
|
1214
|
+
aggregateContent,
|
|
1215
|
+
reasoningDeltas,
|
|
1216
|
+
messageDeltas
|
|
1217
|
+
),
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
if (!run.Graph) {
|
|
1221
|
+
throw new Error('Expected graph to be initialized');
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const graph = run.Graph;
|
|
1225
|
+
const metadata = {
|
|
1226
|
+
thread_id: 'reasoning-fallback-text-delta-block',
|
|
1227
|
+
langgraph_node: 'agent=default',
|
|
1228
|
+
langgraph_step: 1,
|
|
1229
|
+
checkpoint_ns: '',
|
|
1230
|
+
};
|
|
1231
|
+
const runConfig = {
|
|
1232
|
+
...config,
|
|
1233
|
+
configurable: {
|
|
1234
|
+
thread_id: 'reasoning-fallback-text-delta-block',
|
|
1235
|
+
},
|
|
1236
|
+
metadata,
|
|
1237
|
+
};
|
|
1238
|
+
graph.config = runConfig;
|
|
1239
|
+
const stepKey = graph.getStepKey(metadata);
|
|
1240
|
+
await graph.dispatchRunStep(
|
|
1241
|
+
stepKey,
|
|
1242
|
+
{
|
|
1243
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
1244
|
+
message_creation: { message_id: 'msg-text-delta-block' },
|
|
1245
|
+
},
|
|
1246
|
+
metadata
|
|
1247
|
+
);
|
|
1248
|
+
await graph.dispatchMessageDelta(
|
|
1249
|
+
graph.getStepIdByKey(stepKey),
|
|
1250
|
+
{ content: textDeltaContent },
|
|
1251
|
+
metadata
|
|
1252
|
+
);
|
|
1253
|
+
|
|
1254
|
+
graph.overrideModel = new InvokeOnlyMessageModel(
|
|
1255
|
+
new AIMessageChunk({ content: textDeltaContent as never })
|
|
1256
|
+
);
|
|
1257
|
+
|
|
1258
|
+
await graph.createCallModel('default')(
|
|
1259
|
+
{ messages: [new HumanMessage('finish text delta block')] },
|
|
1260
|
+
runConfig
|
|
1261
|
+
);
|
|
1262
|
+
|
|
1263
|
+
expect(messageDeltas.map((delta) => delta.delta.content?.[0])).toEqual([
|
|
1264
|
+
{ type: 'text_delta', text },
|
|
1265
|
+
]);
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
it('sanitizes OpenRouter final reasoning chunks for registered stream handlers', async () => {
|
|
1269
|
+
const firstText = 'Hello ';
|
|
1270
|
+
const replayWithSuffix = 'Hello world';
|
|
1271
|
+
const reasoningText = 'The greeting needs one more word.';
|
|
1272
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
1273
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
1274
|
+
const { aggregateContent } = createContentAggregator();
|
|
1275
|
+
const streamHandler = new ChatModelStreamHandler();
|
|
1276
|
+
const run = await Run.create<t.IState>({
|
|
1277
|
+
runId: 'reasoning-fallback-openrouter-registered-handler-suffix',
|
|
1278
|
+
graphConfig: {
|
|
1279
|
+
type: 'standard',
|
|
1280
|
+
llmConfig: {
|
|
1281
|
+
provider: Providers.OPENROUTER,
|
|
1282
|
+
streamUsage: false,
|
|
1283
|
+
},
|
|
1284
|
+
reasoningKey: 'reasoning',
|
|
1285
|
+
},
|
|
1286
|
+
returnContent: true,
|
|
1287
|
+
skipCleanup: true,
|
|
1288
|
+
customHandlers: {
|
|
1289
|
+
[GraphEvents.CHAT_MODEL_STREAM]: streamHandler,
|
|
1290
|
+
...createReasoningHandlers(
|
|
1291
|
+
aggregateContent,
|
|
1292
|
+
reasoningDeltas,
|
|
1293
|
+
messageDeltas
|
|
1294
|
+
),
|
|
1295
|
+
},
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
if (!run.Graph) {
|
|
1299
|
+
throw new Error('Expected graph to be initialized');
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
1303
|
+
new AIMessageChunk({ content: firstText }),
|
|
1304
|
+
new AIMessageChunk({
|
|
1305
|
+
content: replayWithSuffix,
|
|
1306
|
+
additional_kwargs: {
|
|
1307
|
+
reasoning_details: [{ type: 'reasoning.text', text: reasoningText }],
|
|
1308
|
+
},
|
|
1309
|
+
}),
|
|
1310
|
+
]);
|
|
1311
|
+
|
|
1312
|
+
const finalContentParts = await run.processStream(
|
|
1313
|
+
{ messages: [new HumanMessage('stream OpenRouter suffix once')] },
|
|
1314
|
+
{
|
|
1315
|
+
...config,
|
|
1316
|
+
configurable: {
|
|
1317
|
+
thread_id: 'reasoning-fallback-openrouter-registered-handler-suffix',
|
|
1318
|
+
},
|
|
1319
|
+
}
|
|
1320
|
+
);
|
|
1321
|
+
|
|
1322
|
+
expect(reasoningDeltas).toHaveLength(0);
|
|
1323
|
+
expect(messageDeltas.map((delta) => delta.delta.content?.[0])).toEqual([
|
|
1324
|
+
{ type: ContentTypes.TEXT, text: 'world' },
|
|
1325
|
+
]);
|
|
1326
|
+
expect(finalContentParts).toEqual([
|
|
1327
|
+
{ type: ContentTypes.THINK, think: reasoningText },
|
|
1328
|
+
{ type: ContentTypes.TEXT, text: replayWithSuffix },
|
|
1329
|
+
]);
|
|
1330
|
+
expect(run.getRunMessages()?.[0]?.content).toBe(replayWithSuffix);
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
it('composes observer chat stream handlers with default graph dispatch', async () => {
|
|
1334
|
+
const firstText = 'Vis';
|
|
1335
|
+
const secondText = 'ible answer.';
|
|
1336
|
+
const observedTextChunks: string[] = [];
|
|
1337
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
1338
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
1339
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
1340
|
+
const run = await Run.create<t.IState>({
|
|
1341
|
+
runId: 'reasoning-fallback-openrouter-observer-stream',
|
|
1342
|
+
graphConfig: {
|
|
1343
|
+
type: 'standard',
|
|
1344
|
+
llmConfig: {
|
|
1345
|
+
provider: Providers.OPENROUTER,
|
|
1346
|
+
streamUsage: false,
|
|
1347
|
+
},
|
|
1348
|
+
reasoningKey: 'reasoning',
|
|
1349
|
+
},
|
|
1350
|
+
returnContent: true,
|
|
1351
|
+
skipCleanup: true,
|
|
1352
|
+
customHandlers: {
|
|
1353
|
+
[GraphEvents.CHAT_MODEL_STREAM]: {
|
|
1354
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
1355
|
+
const content = (data.chunk as AIMessageChunk | undefined)?.content;
|
|
1356
|
+
if (typeof content === 'string' && content !== '') {
|
|
1357
|
+
observedTextChunks.push(content);
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
...createReasoningHandlers(
|
|
1362
|
+
aggregateContent,
|
|
1363
|
+
reasoningDeltas,
|
|
1364
|
+
messageDeltas
|
|
1365
|
+
),
|
|
1366
|
+
},
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
if (!run.Graph) {
|
|
1370
|
+
throw new Error('Expected graph to be initialized');
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const model = new ChatOpenRouter({
|
|
1374
|
+
model: 'google/gemini-test',
|
|
1375
|
+
apiKey: 'test-key',
|
|
1376
|
+
});
|
|
1377
|
+
const completions = (model as unknown as StreamingCompletionBackedModel)
|
|
1378
|
+
.completions;
|
|
1379
|
+
|
|
1380
|
+
async function* streamChunks(): AsyncGenerator<OpenAIClient.Chat.Completions.ChatCompletionChunk> {
|
|
1381
|
+
yield createOpenRouterStreamChunk(firstText);
|
|
1382
|
+
yield createOpenRouterStreamChunk(secondText, 'stop');
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
completions.completionWithRetry = async (): Promise<
|
|
1386
|
+
AsyncIterable<OpenAIClient.Chat.Completions.ChatCompletionChunk>
|
|
1387
|
+
> => streamChunks();
|
|
1388
|
+
run.Graph.overrideModel = model as t.ChatModel;
|
|
1389
|
+
|
|
1390
|
+
await run.processStream(
|
|
1391
|
+
{ messages: [new HumanMessage('stream OpenRouter text to observer')] },
|
|
1392
|
+
{
|
|
1393
|
+
...config,
|
|
1394
|
+
configurable: {
|
|
1395
|
+
thread_id: 'reasoning-fallback-openrouter-observer-stream',
|
|
1396
|
+
},
|
|
1397
|
+
}
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
expect(observedTextChunks).toEqual([firstText, secondText]);
|
|
1401
|
+
expect(reasoningDeltas).toHaveLength(0);
|
|
1402
|
+
expect(messageDeltas).toHaveLength(2);
|
|
1403
|
+
expect(contentParts).toEqual([
|
|
1404
|
+
{ type: ContentTypes.TEXT, text: `${firstText}${secondText}` },
|
|
1405
|
+
]);
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
it('does not handle OpenRouter callback-emitted chunks twice inside graph streaming', async () => {
|
|
1409
|
+
const text = 'Visible answer.';
|
|
1410
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
1411
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
1412
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
1413
|
+
const streamHandler = new ChatModelStreamHandler();
|
|
1414
|
+
const run = await Run.create<t.IState>({
|
|
1415
|
+
runId: 'reasoning-fallback-openrouter-callback-yield-stream',
|
|
1416
|
+
graphConfig: {
|
|
1417
|
+
type: 'standard',
|
|
1418
|
+
llmConfig: {
|
|
1419
|
+
provider: Providers.OPENROUTER,
|
|
1420
|
+
streamUsage: false,
|
|
1421
|
+
},
|
|
1422
|
+
reasoningKey: 'reasoning',
|
|
1423
|
+
},
|
|
1424
|
+
returnContent: true,
|
|
1425
|
+
skipCleanup: true,
|
|
1426
|
+
customHandlers: {
|
|
1427
|
+
[GraphEvents.CHAT_MODEL_STREAM]: streamHandler,
|
|
1428
|
+
...createReasoningHandlers(
|
|
1429
|
+
aggregateContent,
|
|
1430
|
+
reasoningDeltas,
|
|
1431
|
+
messageDeltas
|
|
1432
|
+
),
|
|
1433
|
+
},
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
if (!run.Graph) {
|
|
1437
|
+
throw new Error('Expected graph to be initialized');
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const model = new ChatOpenRouter({
|
|
1441
|
+
model: 'google/gemini-test',
|
|
1442
|
+
apiKey: 'test-key',
|
|
1443
|
+
});
|
|
1444
|
+
const completions = (model as unknown as StreamingCompletionBackedModel)
|
|
1445
|
+
.completions;
|
|
1446
|
+
|
|
1447
|
+
async function* streamChunks(): AsyncGenerator<OpenAIClient.Chat.Completions.ChatCompletionChunk> {
|
|
1448
|
+
yield createOpenRouterStreamChunk(text, 'stop');
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
completions.completionWithRetry = async (): Promise<
|
|
1452
|
+
AsyncIterable<OpenAIClient.Chat.Completions.ChatCompletionChunk>
|
|
1453
|
+
> => streamChunks();
|
|
1454
|
+
run.Graph.overrideModel = model as t.ChatModel;
|
|
1455
|
+
|
|
1456
|
+
await run.processStream(
|
|
1457
|
+
{ messages: [new HumanMessage('stream OpenRouter text once')] },
|
|
1458
|
+
{
|
|
1459
|
+
...config,
|
|
1460
|
+
configurable: {
|
|
1461
|
+
thread_id: 'reasoning-fallback-openrouter-callback-yield-stream',
|
|
1462
|
+
},
|
|
1463
|
+
}
|
|
1464
|
+
);
|
|
1465
|
+
|
|
1466
|
+
expect(reasoningDeltas).toHaveLength(0);
|
|
1467
|
+
expect(messageDeltas).toHaveLength(1);
|
|
1468
|
+
expect(contentParts).toEqual([{ type: ContentTypes.TEXT, text }]);
|
|
1469
|
+
});
|
|
1470
|
+
|
|
558
1471
|
it.each([
|
|
559
1472
|
{
|
|
560
1473
|
providerName: 'DeepSeek',
|