@illuma-ai/agents 1.5.1 → 2.1.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/README.md +0 -62
- package/dist/cjs/agents/AgentContext.cjs +160 -259
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +12 -12
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +30 -13
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +1 -1
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/matchers.cjs +2 -2
- package/dist/cjs/hooks/matchers.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -1
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -5
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +33 -61
- package/dist/cjs/llm/bedrock/index.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 +10 -27
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +3 -84
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/memory/citations.cjs +4 -4
- package/dist/cjs/memory/citations.cjs.map +1 -1
- package/dist/cjs/memory/constants.cjs +17 -17
- package/dist/cjs/memory/constants.cjs.map +1 -1
- package/dist/cjs/memory/mmr.cjs +1 -1
- package/dist/cjs/memory/mmr.cjs.map +1 -1
- package/dist/cjs/memory/paths.cjs +1 -1
- package/dist/cjs/memory/paths.cjs.map +1 -1
- package/dist/cjs/memory/recallTracking.cjs +3 -3
- package/dist/cjs/memory/recallTracking.cjs.map +1 -1
- package/dist/cjs/memory/temporalDecay.cjs +2 -2
- package/dist/cjs/memory/temporalDecay.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +0 -89
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +13 -71
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +11 -21
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +13 -41
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +11 -16
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +78 -13
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/memory/memoryAppendTool.cjs +1 -1
- package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -1
- package/dist/cjs/tools/memory/memoryGetTool.cjs +2 -2
- package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -1
- package/dist/cjs/tools/memory/memorySearchTool.cjs +3 -3
- package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -1
- package/dist/cjs/tools/memory/shared.cjs +1 -1
- package/dist/cjs/tools/memory/shared.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +3 -11
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -28
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +3 -10
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +48 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/types/graph.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +160 -259
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +12 -12
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +30 -13
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs +1 -1
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +1 -1
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/matchers.mjs +2 -2
- package/dist/esm/hooks/matchers.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -1
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -5
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +34 -61
- package/dist/esm/llm/bedrock/index.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 +10 -27
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -5
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/memory/citations.mjs +4 -4
- package/dist/esm/memory/citations.mjs.map +1 -1
- package/dist/esm/memory/constants.mjs +17 -17
- package/dist/esm/memory/constants.mjs.map +1 -1
- package/dist/esm/memory/mmr.mjs +1 -1
- package/dist/esm/memory/mmr.mjs.map +1 -1
- package/dist/esm/memory/paths.mjs +1 -1
- package/dist/esm/memory/paths.mjs.map +1 -1
- package/dist/esm/memory/recallTracking.mjs +3 -3
- package/dist/esm/memory/recallTracking.mjs.map +1 -1
- package/dist/esm/memory/temporalDecay.mjs +2 -2
- package/dist/esm/memory/temporalDecay.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +0 -89
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +13 -71
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +12 -22
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +14 -41
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +12 -17
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +78 -13
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/memory/memoryAppendTool.mjs +1 -1
- package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -1
- package/dist/esm/tools/memory/memoryGetTool.mjs +2 -2
- package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -1
- package/dist/esm/tools/memory/memorySearchTool.mjs +3 -3
- package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -1
- package/dist/esm/tools/memory/shared.mjs +1 -1
- package/dist/esm/tools/memory/shared.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +3 -11
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -28
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +3 -10
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +48 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/types/graph.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +25 -95
- package/dist/types/common/enum.d.ts +12 -12
- package/dist/types/graphs/Graph.d.ts +2 -2
- package/dist/types/graphs/phases/memoryFlushPhase.d.ts +2 -2
- package/dist/types/hooks/HookRegistry.d.ts +1 -1
- package/dist/types/hooks/matchers.d.ts +2 -2
- package/dist/types/hooks/types.d.ts +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/llm/bedrock/index.d.ts +1 -54
- package/dist/types/llm/openai/index.d.ts +1 -1
- package/dist/types/memory/citations.d.ts +4 -4
- package/dist/types/memory/constants.d.ts +17 -17
- package/dist/types/memory/mmr.d.ts +3 -3
- package/dist/types/memory/paths.d.ts +1 -1
- package/dist/types/memory/temporalDecay.d.ts +2 -2
- package/dist/types/memory/types.d.ts +3 -3
- package/dist/types/messages/format.d.ts +2 -5
- package/dist/types/tools/CodeExecutor.d.ts +0 -6
- package/dist/types/tools/ToolNode.d.ts +3 -3
- package/dist/types/tools/memory/shared.d.ts +1 -1
- package/dist/types/tools/search/test.d.ts +1 -0
- package/dist/types/tools/search/types.d.ts +5 -99
- package/dist/types/tools/search/utils.d.ts +2 -2
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +29 -0
- package/dist/types/types/graph.d.ts +30 -34
- package/dist/types/types/index.d.ts +0 -1
- package/dist/types/types/messages.d.ts +1 -1
- package/dist/types/types/run.d.ts +1 -3
- package/dist/types/types/tools.d.ts +5 -14
- package/package.json +1 -61
- package/src/agents/AgentContext.test.ts +176 -0
- package/src/agents/AgentContext.ts +179 -305
- package/src/agents/__tests__/AgentContext.test.ts +0 -632
- package/src/common/__tests__/enum.test.ts +1 -1
- package/src/common/enum.ts +12 -12
- package/src/graphs/Graph.ts +32 -13
- package/src/graphs/MultiAgentGraph.ts +1 -1
- package/src/graphs/gapFeatures.test.ts +1 -1
- package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +1 -1
- package/src/graphs/phases/memoryFlushPhase.ts +2 -2
- package/src/hooks/HookRegistry.ts +1 -1
- package/src/hooks/index.ts +1 -1
- package/src/hooks/matchers.ts +2 -2
- package/src/hooks/types.ts +1 -1
- package/src/index.ts +0 -6
- package/src/llm/anthropic/utils/message_inputs.ts +1 -10
- package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +18 -166
- package/src/llm/bedrock/index.ts +41 -116
- package/src/llm/openai/index.ts +2 -2
- package/src/llm/openai/utils/index.ts +14 -31
- package/src/memory/citations.ts +4 -4
- package/src/memory/constants.ts +17 -17
- package/src/memory/mmr.ts +3 -3
- package/src/memory/paths.ts +1 -1
- package/src/memory/recallTracking.ts +3 -3
- package/src/memory/temporalDecay.ts +2 -2
- package/src/memory/types.ts +3 -3
- package/src/messages/cache.test.ts +24 -62
- package/src/messages/cache.ts +0 -112
- package/src/messages/ensureThinkingBlock.test.ts +1 -1
- package/src/messages/format.ts +13 -92
- package/src/messages/formatAgentMessages.test.ts +1 -1
- package/src/scripts/subagent-configurable-inheritance.ts +263 -0
- package/src/scripts/subagent-event-driven-debug.ts +2 -2
- package/src/specs/anthropic.simple.test.ts +0 -61
- package/src/specs/prune.orphans.test.ts +1 -1
- package/src/tools/BashExecutor.ts +13 -37
- package/src/tools/CodeExecutor.ts +14 -59
- package/src/tools/ProgrammaticToolCalling.ts +14 -29
- package/src/tools/ToolNode.ts +75 -14
- package/src/tools/__tests__/CodeExecutor.test.ts +3 -3
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -60
- package/src/tools/__tests__/SubagentExecutor.test.ts +157 -0
- package/src/tools/memory/memoryAppendTool.ts +1 -1
- package/src/tools/memory/memoryGetTool.ts +2 -2
- package/src/tools/memory/memorySearchTool.ts +3 -3
- package/src/tools/memory/shared.ts +1 -1
- package/src/tools/search/output.md +2775 -0
- package/src/tools/search/search.ts +2 -12
- package/src/tools/search/test.html +884 -0
- package/src/tools/search/test.md +643 -0
- package/src/tools/search/test.ts +159 -0
- package/src/tools/search/tool.ts +2 -36
- package/src/tools/search/types.ts +8 -133
- package/src/tools/search/utils.ts +5 -13
- package/src/tools/subagent/SubagentExecutor.ts +78 -0
- package/src/types/graph.ts +27 -34
- package/src/types/index.ts +0 -1
- package/src/types/messages.ts +1 -1
- package/src/types/run.ts +1 -3
- package/src/types/tools.ts +5 -14
- package/dist/cjs/langchain/google-common.cjs +0 -3
- package/dist/cjs/langchain/google-common.cjs.map +0 -1
- package/dist/cjs/langchain/index.cjs +0 -86
- package/dist/cjs/langchain/index.cjs.map +0 -1
- package/dist/cjs/langchain/language_models/chat_models.cjs +0 -3
- package/dist/cjs/langchain/language_models/chat_models.cjs.map +0 -1
- package/dist/cjs/langchain/messages/tool.cjs +0 -3
- package/dist/cjs/langchain/messages/tool.cjs.map +0 -1
- package/dist/cjs/langchain/messages.cjs +0 -51
- package/dist/cjs/langchain/messages.cjs.map +0 -1
- package/dist/cjs/langchain/openai.cjs +0 -3
- package/dist/cjs/langchain/openai.cjs.map +0 -1
- package/dist/cjs/langchain/prompts.cjs +0 -11
- package/dist/cjs/langchain/prompts.cjs.map +0 -1
- package/dist/cjs/langchain/runnables.cjs +0 -19
- package/dist/cjs/langchain/runnables.cjs.map +0 -1
- package/dist/cjs/langchain/tools.cjs +0 -23
- package/dist/cjs/langchain/tools.cjs.map +0 -1
- package/dist/cjs/langchain/utils/env.cjs +0 -11
- package/dist/cjs/langchain/utils/env.cjs.map +0 -1
- package/dist/cjs/llm/bedrock/cacheSupport.cjs +0 -55
- package/dist/cjs/llm/bedrock/cacheSupport.cjs.map +0 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +0 -189
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +0 -1
- package/dist/cjs/tools/search/tavily-search.cjs +0 -372
- package/dist/cjs/tools/search/tavily-search.cjs.map +0 -1
- package/dist/cjs/types/agent-cache.cjs +0 -54
- package/dist/cjs/types/agent-cache.cjs.map +0 -1
- package/dist/esm/langchain/google-common.mjs +0 -2
- package/dist/esm/langchain/google-common.mjs.map +0 -1
- package/dist/esm/langchain/index.mjs +0 -5
- package/dist/esm/langchain/index.mjs.map +0 -1
- package/dist/esm/langchain/language_models/chat_models.mjs +0 -2
- package/dist/esm/langchain/language_models/chat_models.mjs.map +0 -1
- package/dist/esm/langchain/messages/tool.mjs +0 -2
- package/dist/esm/langchain/messages/tool.mjs.map +0 -1
- package/dist/esm/langchain/messages.mjs +0 -2
- package/dist/esm/langchain/messages.mjs.map +0 -1
- package/dist/esm/langchain/openai.mjs +0 -2
- package/dist/esm/langchain/openai.mjs.map +0 -1
- package/dist/esm/langchain/prompts.mjs +0 -2
- package/dist/esm/langchain/prompts.mjs.map +0 -1
- package/dist/esm/langchain/runnables.mjs +0 -2
- package/dist/esm/langchain/runnables.mjs.map +0 -1
- package/dist/esm/langchain/tools.mjs +0 -2
- package/dist/esm/langchain/tools.mjs.map +0 -1
- package/dist/esm/langchain/utils/env.mjs +0 -2
- package/dist/esm/langchain/utils/env.mjs.map +0 -1
- package/dist/esm/llm/bedrock/cacheSupport.mjs +0 -52
- package/dist/esm/llm/bedrock/cacheSupport.mjs.map +0 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +0 -186
- package/dist/esm/tools/search/tavily-scraper.mjs.map +0 -1
- package/dist/esm/tools/search/tavily-search.mjs +0 -370
- package/dist/esm/tools/search/tavily-search.mjs.map +0 -1
- package/dist/esm/types/agent-cache.mjs +0 -52
- package/dist/esm/types/agent-cache.mjs.map +0 -1
- package/dist/types/langchain/google-common.d.ts +0 -1
- package/dist/types/langchain/index.d.ts +0 -8
- package/dist/types/langchain/language_models/chat_models.d.ts +0 -1
- package/dist/types/langchain/messages/tool.d.ts +0 -1
- package/dist/types/langchain/messages.d.ts +0 -2
- package/dist/types/langchain/openai.d.ts +0 -1
- package/dist/types/langchain/prompts.d.ts +0 -1
- package/dist/types/langchain/runnables.d.ts +0 -2
- package/dist/types/langchain/tools.d.ts +0 -2
- package/dist/types/langchain/utils/env.d.ts +0 -1
- package/dist/types/llm/bedrock/cacheSupport.d.ts +0 -35
- package/dist/types/tools/search/tavily-scraper.d.ts +0 -19
- package/dist/types/tools/search/tavily-search.d.ts +0 -4
- package/dist/types/tools/subagent/types.d.ts +0 -84
- package/dist/types/types/agent-cache.d.ts +0 -71
- package/src/agents/__tests__/AgentContext.cacheTtl.live.test.ts +0 -259
- package/src/agents/__tests__/AgentContext.crossAgentTier1.live.test.ts +0 -266
- package/src/agents/__tests__/AgentContext.crossUserCache.live.test.ts +0 -342
- package/src/langchain/google-common.ts +0 -1
- package/src/langchain/index.ts +0 -8
- package/src/langchain/language_models/chat_models.ts +0 -1
- package/src/langchain/messages/tool.ts +0 -5
- package/src/langchain/messages.ts +0 -21
- package/src/langchain/openai.ts +0 -1
- package/src/langchain/prompts.ts +0 -1
- package/src/langchain/runnables.ts +0 -7
- package/src/langchain/tools.ts +0 -8
- package/src/langchain/utils/env.ts +0 -1
- package/src/llm/anthropic/utils/server-tool-inputs.test.ts +0 -436
- package/src/llm/bedrock/cacheSupport.test.ts +0 -99
- package/src/llm/bedrock/cacheSupport.ts +0 -53
- package/src/tools/search/tavily-scraper.ts +0 -235
- package/src/tools/search/tavily-search.ts +0 -424
- package/src/tools/search/tavily.test.ts +0 -965
- package/src/tools/subagent/types.test.ts +0 -70
- package/src/tools/subagent/types.ts +0 -115
- package/src/types/agent-cache.ts +0 -74
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.mjs","sources":["../../../src/memory/paths.ts"],"sourcesContent":["/**\n * Autonomous memory — canonical path whitelist + tier utilities.\n *\n * Single source of truth for the 8 stable memory documents. Every write\n * goes through {@link assertWritablePath}; every reader can ask\n * {@link getTierForPath} what a row belongs to. Adding or removing a\n * path means editing this file — and exactly this file.\n *\n * ## Why a whitelist?\n *\n * Earlier historical designs used date-keyed files\n * (`memory/YYYY-MM-DD.md`), which have three problems for a persistent\n * multi-user agent:\n *\n * 1. **Unbounded growth** — one row per day per agent per user, forever.\n * 2. **LLM routing ambiguity** — \"which date file do I read?\" has no\n * good answer, so the model reads (and ranks against) all of them.\n * 3. **No natural deduplication** — the same fact written across three\n * days returns three near-identical hits.\n *\n * With 8 stable canonical documents the LLM knows *exactly* where to\n * write (\"is this a preference? → `memory/user/preferences.md`\") and\n * each row accumulates via UPSERT instead of piling up new rows.\n *\n * ## Two tiers\n *\n * - **Agent tier** (`memory/agent/*`) — operational knowledge that\n * every caller of the agent benefits from. Stored with `user_id = NULL`.\n * Shared across all users of a collaborative agent; the only tier that\n * exists for isolated/autonomous agents with no invoker.\n *\n * - **User tier** (`memory/user/*`) — per-invoker personalization.\n * Stored with `user_id = <caller>`. Read path filters so User A never\n * sees User B's rows, even for the same agent. Not writable unless the\n * flush scope carries a non-empty `userId`.\n *\n * The tier of a path is a function of its prefix (`memory/agent/` vs\n * `memory/user/`), so adding a new document is one line here + the\n * constant array entry; no store or route code has to change.\n */\n\nimport { MEMORY_PATH_PREFIX } from './constants';\nimport type { MemoryScope } from './types';\n\n/** Tier discriminator — the two kinds of memory a row can belong to. */\nexport type MemoryTier = 'agent' | 'user';\n\n/** Every canonical document the flush phase is allowed to write. */\nexport interface MemoryPathDescriptor {\n /** Full path including the `memory/` prefix. */\n path: string;\n /** Which tier the path lives under. */\n tier: MemoryTier;\n /** Short label shown in UI tier badges and the flush prompt rubric. */\n label: string;\n /** One-line description — fed to the LLM to make routing unambiguous. */\n description: string;\n}\n\n/** Agent-tier documents — shared across all users of the agent. */\nexport const MEMORY_AGENT_PATHS: readonly MemoryPathDescriptor[] =\n Object.freeze([\n Object.freeze({\n path: 'memory/agent/playbook.md',\n tier: 'agent' as const,\n label: 'Playbook',\n description:\n 'Successful task patterns and workflows that have worked for this agent — ' +\n 'reusable recipes, known-good tool sequences, approaches that consistently ' +\n 'produce the right result.',\n }),\n Object.freeze({\n path: 'memory/agent/pitfalls.md',\n tier: 'agent' as const,\n label: 'Pitfalls',\n description:\n 'Tool failures, error recoveries, schema mistakes, and invalid argument ' +\n 'shapes the agent has seen — so the same mistake is not repeated on future ' +\n 'turns. One note per distinct failure mode.',\n }),\n Object.freeze({\n path: 'memory/agent/domain.md',\n tier: 'agent' as const,\n label: 'Domain',\n description:\n 'Durable facts about the systems, APIs, data models, and business rules ' +\n 'this agent operates on — things that are true independent of any one user ' +\n 'and that the agent repeatedly needs to know.',\n }),\n Object.freeze({\n path: 'memory/agent/style.md',\n tier: 'agent' as const,\n label: 'Style',\n description:\n 'Tone, formatting, and response-shape conventions the agent has converged ' +\n 'on across the whole user base. Do NOT write user-specific preferences here ' +\n '(those belong under the user tier).',\n }),\n ]);\n\n/** User-tier documents — per-invoker, never shared across users. */\nexport const MEMORY_USER_PATHS: readonly MemoryPathDescriptor[] = Object.freeze(\n [\n Object.freeze({\n path: 'memory/user/profile.md',\n tier: 'user' as const,\n label: 'Profile',\n description:\n \"This specific user's identity — name, role, team, sign-off name, \" +\n 'responsibilities, stable facts about who they are. Only facts ' +\n 'volunteered by the user themselves.',\n }),\n Object.freeze({\n path: 'memory/user/preferences.md',\n tier: 'user' as const,\n label: 'Preferences',\n description:\n 'How THIS user wants things done — preferred formats, verbosity, tone, ' +\n 'cadence, output length, language conventions. Explicit corrections they ' +\n 'have given count as preferences.',\n }),\n Object.freeze({\n path: 'memory/user/projects.md',\n tier: 'user' as const,\n label: 'Projects',\n description:\n \"This user's current initiatives, ongoing work, deadlines, and active \" +\n 'project context. Include dates in the prose when relevant. Retire entries ' +\n 'by overwriting them on future flushes when the user indicates a project is done.',\n }),\n Object.freeze({\n path: 'memory/user/references.md',\n tier: 'user' as const,\n label: 'References',\n description:\n 'External systems, dashboards, accounts, links, channels, and resources ' +\n \"THIS user has pointed the agent at — the 'where to look' pointers that \" +\n 'make later turns more efficient.',\n }),\n ]\n);\n\n/** All 8 canonical documents in display order: agent tier first, user tier second. */\nexport const MEMORY_ALL_PATHS: readonly MemoryPathDescriptor[] = Object.freeze([\n ...MEMORY_AGENT_PATHS,\n ...MEMORY_USER_PATHS,\n]);\n\n/** Fast O(1) lookup map: path → descriptor. */\nconst PATH_INDEX: ReadonlyMap<string, MemoryPathDescriptor> = new Map(\n MEMORY_ALL_PATHS.map((p) => [p.path, p])\n);\n\n/** Set of all whitelisted paths — for `has()` lookups. */\nexport const MEMORY_WRITABLE_PATHS: ReadonlySet<string> = new Set(\n MEMORY_ALL_PATHS.map((p) => p.path)\n);\n\n/** Returns the descriptor for a canonical path, or `undefined` if not on the whitelist. */\nexport function getPathDescriptor(\n path: string\n): MemoryPathDescriptor | undefined {\n return PATH_INDEX.get(path);\n}\n\n/**\n * Returns the tier a given path belongs to.\n *\n * For unknown paths (e.g. legacy date-keyed rows that predate this schema),\n * falls back to inspecting the `memory/agent/` or `memory/user/` prefix.\n * Anything that matches neither is classified as `agent` — it will surface\n * in the admin UI under the agent-tier header and stay read-only there.\n */\nexport function getTierForPath(path: string): MemoryTier {\n const descriptor = PATH_INDEX.get(path);\n if (descriptor) return descriptor.tier;\n if (path.startsWith('memory/user/')) return 'user';\n return 'agent';\n}\n\n/**\n * Paths that are legal to write **for this caller's scope**.\n *\n * If `scope.userId` is absent (isolated/autonomous agent), the user-tier\n * paths are dropped. This is the list the flush prompt renders into its\n * rubric — the LLM never sees paths it cannot write to.\n */\nexport function getWritablePathsForScope(\n scope: Pick<MemoryScope, 'userId'>\n): readonly MemoryPathDescriptor[] {\n if (scope.userId == null || scope.userId === '') {\n return MEMORY_AGENT_PATHS;\n }\n return MEMORY_ALL_PATHS;\n}\n\n/**\n * Validates a write path against the whitelist AND the caller's scope.\n *\n * Throws with an actionable message when:\n * - the path is missing the `memory/` prefix\n * - the path is not on the 8-doc whitelist\n * - the path is user-tier but `scope.userId` is missing\n *\n * The store's append() is the single call site; other callers should\n * never hit this directly. Kept as a standalone function for testability.\n */\nexport function assertWritablePath(\n path: string,\n scope: Pick<MemoryScope, 'userId'>\n): MemoryPathDescriptor {\n if (!path || !path.startsWith(MEMORY_PATH_PREFIX)) {\n throw new Error(\n `memory_append path must start with \"${MEMORY_PATH_PREFIX}\" (received \"${path}\")`\n );\n }\n const descriptor = PATH_INDEX.get(path);\n if (!descriptor) {\n throw new Error(\n `memory_append path \"${path}\" is not on the canonical whitelist. ` +\n `Allowed: ${MEMORY_ALL_PATHS.map((p) => p.path).join(', ')}`\n );\n }\n if (\n descriptor.tier === 'user' &&\n (scope.userId == null || scope.userId === '')\n ) {\n throw new Error(\n `memory_append to user-tier path \"${path}\" requires a caller userId in scope, ` +\n `but scope.userId is missing. This path is private to a specific user and ` +\n `cannot be written from an isolated/autonomous context.`\n );\n }\n return descriptor;\n}\n\n/**\n * Renders the whitelist as a compact bullet list, used inside the flush\n * prompt so the LLM sees the authoritative rubric at inference time.\n *\n * Output shape:\n * - memory/agent/playbook.md [Playbook, shared] — <description>\n * - memory/agent/pitfalls.md [Pitfalls, shared] — <description>\n * ...\n *\n * The `[Label, shared|private]` tag tells the model whether the doc is\n * visible to all users (agent tier) or only to the caller (user tier).\n * Filtered to only the paths writable for the given scope.\n */\nexport function renderPathsRubric(scope: Pick<MemoryScope, 'userId'>): string {\n const writable = getWritablePathsForScope(scope);\n return writable\n .map((p) => {\n const visibility = p.tier === 'agent' ? 'shared' : 'private';\n return `- ${p.path} [${p.label}, ${visibility}] — ${p.description}`;\n })\n .join('\\n');\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"paths.mjs","sources":["../../../src/memory/paths.ts"],"sourcesContent":["/**\n * Autonomous memory — canonical path whitelist + tier utilities.\n *\n * Single source of truth for the 8 stable memory documents. Every write\n * goes through {@link assertWritablePath}; every reader can ask\n * {@link getTierForPath} what a row belongs to. Adding or removing a\n * path means editing this file — and exactly this file.\n *\n * ## Why a whitelist?\n *\n * Earlier upstream-faithful designs used date-keyed files\n * (`memory/YYYY-MM-DD.md`), which have three problems for a persistent\n * multi-user agent:\n *\n * 1. **Unbounded growth** — one row per day per agent per user, forever.\n * 2. **LLM routing ambiguity** — \"which date file do I read?\" has no\n * good answer, so the model reads (and ranks against) all of them.\n * 3. **No natural deduplication** — the same fact written across three\n * days returns three near-identical hits.\n *\n * With 8 stable canonical documents the LLM knows *exactly* where to\n * write (\"is this a preference? → `memory/user/preferences.md`\") and\n * each row accumulates via UPSERT instead of piling up new rows.\n *\n * ## Two tiers\n *\n * - **Agent tier** (`memory/agent/*`) — operational knowledge that\n * every caller of the agent benefits from. Stored with `user_id = NULL`.\n * Shared across all users of a collaborative agent; the only tier that\n * exists for isolated/autonomous agents with no invoker.\n *\n * - **User tier** (`memory/user/*`) — per-invoker personalization.\n * Stored with `user_id = <caller>`. Read path filters so User A never\n * sees User B's rows, even for the same agent. Not writable unless the\n * flush scope carries a non-empty `userId`.\n *\n * The tier of a path is a function of its prefix (`memory/agent/` vs\n * `memory/user/`), so adding a new document is one line here + the\n * constant array entry; no store or route code has to change.\n */\n\nimport { MEMORY_PATH_PREFIX } from './constants';\nimport type { MemoryScope } from './types';\n\n/** Tier discriminator — the two kinds of memory a row can belong to. */\nexport type MemoryTier = 'agent' | 'user';\n\n/** Every canonical document the flush phase is allowed to write. */\nexport interface MemoryPathDescriptor {\n /** Full path including the `memory/` prefix. */\n path: string;\n /** Which tier the path lives under. */\n tier: MemoryTier;\n /** Short label shown in UI tier badges and the flush prompt rubric. */\n label: string;\n /** One-line description — fed to the LLM to make routing unambiguous. */\n description: string;\n}\n\n/** Agent-tier documents — shared across all users of the agent. */\nexport const MEMORY_AGENT_PATHS: readonly MemoryPathDescriptor[] =\n Object.freeze([\n Object.freeze({\n path: 'memory/agent/playbook.md',\n tier: 'agent' as const,\n label: 'Playbook',\n description:\n 'Successful task patterns and workflows that have worked for this agent — ' +\n 'reusable recipes, known-good tool sequences, approaches that consistently ' +\n 'produce the right result.',\n }),\n Object.freeze({\n path: 'memory/agent/pitfalls.md',\n tier: 'agent' as const,\n label: 'Pitfalls',\n description:\n 'Tool failures, error recoveries, schema mistakes, and invalid argument ' +\n 'shapes the agent has seen — so the same mistake is not repeated on future ' +\n 'turns. One note per distinct failure mode.',\n }),\n Object.freeze({\n path: 'memory/agent/domain.md',\n tier: 'agent' as const,\n label: 'Domain',\n description:\n 'Durable facts about the systems, APIs, data models, and business rules ' +\n 'this agent operates on — things that are true independent of any one user ' +\n 'and that the agent repeatedly needs to know.',\n }),\n Object.freeze({\n path: 'memory/agent/style.md',\n tier: 'agent' as const,\n label: 'Style',\n description:\n 'Tone, formatting, and response-shape conventions the agent has converged ' +\n 'on across the whole user base. Do NOT write user-specific preferences here ' +\n '(those belong under the user tier).',\n }),\n ]);\n\n/** User-tier documents — per-invoker, never shared across users. */\nexport const MEMORY_USER_PATHS: readonly MemoryPathDescriptor[] = Object.freeze(\n [\n Object.freeze({\n path: 'memory/user/profile.md',\n tier: 'user' as const,\n label: 'Profile',\n description:\n \"This specific user's identity — name, role, team, sign-off name, \" +\n 'responsibilities, stable facts about who they are. Only facts ' +\n 'volunteered by the user themselves.',\n }),\n Object.freeze({\n path: 'memory/user/preferences.md',\n tier: 'user' as const,\n label: 'Preferences',\n description:\n 'How THIS user wants things done — preferred formats, verbosity, tone, ' +\n 'cadence, output length, language conventions. Explicit corrections they ' +\n 'have given count as preferences.',\n }),\n Object.freeze({\n path: 'memory/user/projects.md',\n tier: 'user' as const,\n label: 'Projects',\n description:\n \"This user's current initiatives, ongoing work, deadlines, and active \" +\n 'project context. Include dates in the prose when relevant. Retire entries ' +\n 'by overwriting them on future flushes when the user indicates a project is done.',\n }),\n Object.freeze({\n path: 'memory/user/references.md',\n tier: 'user' as const,\n label: 'References',\n description:\n 'External systems, dashboards, accounts, links, channels, and resources ' +\n \"THIS user has pointed the agent at — the 'where to look' pointers that \" +\n 'make later turns more efficient.',\n }),\n ]\n);\n\n/** All 8 canonical documents in display order: agent tier first, user tier second. */\nexport const MEMORY_ALL_PATHS: readonly MemoryPathDescriptor[] = Object.freeze([\n ...MEMORY_AGENT_PATHS,\n ...MEMORY_USER_PATHS,\n]);\n\n/** Fast O(1) lookup map: path → descriptor. */\nconst PATH_INDEX: ReadonlyMap<string, MemoryPathDescriptor> = new Map(\n MEMORY_ALL_PATHS.map((p) => [p.path, p])\n);\n\n/** Set of all whitelisted paths — for `has()` lookups. */\nexport const MEMORY_WRITABLE_PATHS: ReadonlySet<string> = new Set(\n MEMORY_ALL_PATHS.map((p) => p.path)\n);\n\n/** Returns the descriptor for a canonical path, or `undefined` if not on the whitelist. */\nexport function getPathDescriptor(\n path: string\n): MemoryPathDescriptor | undefined {\n return PATH_INDEX.get(path);\n}\n\n/**\n * Returns the tier a given path belongs to.\n *\n * For unknown paths (e.g. legacy date-keyed rows that predate this schema),\n * falls back to inspecting the `memory/agent/` or `memory/user/` prefix.\n * Anything that matches neither is classified as `agent` — it will surface\n * in the admin UI under the agent-tier header and stay read-only there.\n */\nexport function getTierForPath(path: string): MemoryTier {\n const descriptor = PATH_INDEX.get(path);\n if (descriptor) return descriptor.tier;\n if (path.startsWith('memory/user/')) return 'user';\n return 'agent';\n}\n\n/**\n * Paths that are legal to write **for this caller's scope**.\n *\n * If `scope.userId` is absent (isolated/autonomous agent), the user-tier\n * paths are dropped. This is the list the flush prompt renders into its\n * rubric — the LLM never sees paths it cannot write to.\n */\nexport function getWritablePathsForScope(\n scope: Pick<MemoryScope, 'userId'>\n): readonly MemoryPathDescriptor[] {\n if (scope.userId == null || scope.userId === '') {\n return MEMORY_AGENT_PATHS;\n }\n return MEMORY_ALL_PATHS;\n}\n\n/**\n * Validates a write path against the whitelist AND the caller's scope.\n *\n * Throws with an actionable message when:\n * - the path is missing the `memory/` prefix\n * - the path is not on the 8-doc whitelist\n * - the path is user-tier but `scope.userId` is missing\n *\n * The store's append() is the single call site; other callers should\n * never hit this directly. Kept as a standalone function for testability.\n */\nexport function assertWritablePath(\n path: string,\n scope: Pick<MemoryScope, 'userId'>\n): MemoryPathDescriptor {\n if (!path || !path.startsWith(MEMORY_PATH_PREFIX)) {\n throw new Error(\n `memory_append path must start with \"${MEMORY_PATH_PREFIX}\" (received \"${path}\")`\n );\n }\n const descriptor = PATH_INDEX.get(path);\n if (!descriptor) {\n throw new Error(\n `memory_append path \"${path}\" is not on the canonical whitelist. ` +\n `Allowed: ${MEMORY_ALL_PATHS.map((p) => p.path).join(', ')}`\n );\n }\n if (\n descriptor.tier === 'user' &&\n (scope.userId == null || scope.userId === '')\n ) {\n throw new Error(\n `memory_append to user-tier path \"${path}\" requires a caller userId in scope, ` +\n `but scope.userId is missing. This path is private to a specific user and ` +\n `cannot be written from an isolated/autonomous context.`\n );\n }\n return descriptor;\n}\n\n/**\n * Renders the whitelist as a compact bullet list, used inside the flush\n * prompt so the LLM sees the authoritative rubric at inference time.\n *\n * Output shape:\n * - memory/agent/playbook.md [Playbook, shared] — <description>\n * - memory/agent/pitfalls.md [Pitfalls, shared] — <description>\n * ...\n *\n * The `[Label, shared|private]` tag tells the model whether the doc is\n * visible to all users (agent tier) or only to the caller (user tier).\n * Filtered to only the paths writable for the given scope.\n */\nexport function renderPathsRubric(scope: Pick<MemoryScope, 'userId'>): string {\n const writable = getWritablePathsForScope(scope);\n return writable\n .map((p) => {\n const visibility = p.tier === 'agent' ? 'shared' : 'private';\n return `- ${p.path} [${p.label}, ${visibility}] — ${p.description}`;\n })\n .join('\\n');\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,9 +3,9 @@ import { createHash } from 'crypto';
|
|
|
3
3
|
/**
|
|
4
4
|
* Recall tracking — Phase 2.
|
|
5
5
|
*
|
|
6
|
-
* Lightweight adaptation of
|
|
6
|
+
* Lightweight adaptation of upstream
|
|
7
7
|
* `extensions/memory-core/src/short-term-promotion.ts::recordShortTermRecalls`.
|
|
8
|
-
*
|
|
8
|
+
* Upstream stores recalls in a JSON file under `memory/.dreams/`; we store
|
|
9
9
|
* them in a Postgres table `agent_memory_recalls`. Schema captures what the
|
|
10
10
|
* future Phase 3 dreaming/promotion algorithm will need:
|
|
11
11
|
* - which memory row was surfaced (`memory_id`)
|
|
@@ -65,7 +65,7 @@ class PgvectorRecallTracker {
|
|
|
65
65
|
const qhash = hashQuery(params.query);
|
|
66
66
|
const bucket = dayBucket(nowMs);
|
|
67
67
|
// [recall-tracking] debug: upsert one row per (agent, memory, query, day)
|
|
68
|
-
//
|
|
68
|
+
// Upstream dedupes per-day per-query so repeated searches don't inflate counts.
|
|
69
69
|
const values = [];
|
|
70
70
|
const args = [];
|
|
71
71
|
let i = 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recallTracking.mjs","sources":["../../../src/memory/recallTracking.ts"],"sourcesContent":["/**\n * Recall tracking — Phase 2.\n *\n * Lightweight adaptation of
|
|
1
|
+
{"version":3,"file":"recallTracking.mjs","sources":["../../../src/memory/recallTracking.ts"],"sourcesContent":["/**\n * Recall tracking — Phase 2.\n *\n * Lightweight adaptation of upstream\n * `extensions/memory-core/src/short-term-promotion.ts::recordShortTermRecalls`.\n * Upstream stores recalls in a JSON file under `memory/.dreams/`; we store\n * them in a Postgres table `agent_memory_recalls`. Schema captures what the\n * future Phase 3 dreaming/promotion algorithm will need:\n * - which memory row was surfaced (`memory_id`)\n * - the query that surfaced it (raw + SHA-256 hash for dedupe)\n * - hybrid score at the time of recall\n * - the day bucket (for per-day dedupe / frequency counting)\n * - the recorded timestamp\n *\n * Best-effort: failures never block memory_search. The caller fires\n * {@link RecallTracker.record} without awaiting the result and ignores errors.\n */\nimport { createHash } from 'crypto';\nimport type { Pool } from 'pg';\n\nexport interface RecallTracker {\n /** Record that the given memory ids were surfaced to the model for a query. */\n record(params: RecallRecordParams): Promise<void>;\n /** Backend-specific schema migration. Idempotent. */\n migrate(): Promise<void>;\n}\n\nexport interface RecallRecordParams {\n agentId: string;\n query: string;\n hits: Array<{ id: string; path: string; score: number }>;\n nowMs?: number;\n}\n\nexport const RECALL_TABLE = 'agent_memory_recalls';\n\nfunction hashQuery(query: string): string {\n return createHash('sha256')\n .update(query.trim().toLowerCase())\n .digest('hex')\n .slice(0, 32);\n}\n\nfunction dayBucket(nowMs: number): string {\n const d = new Date(nowMs);\n const y = d.getUTCFullYear();\n const m = String(d.getUTCMonth() + 1).padStart(2, '0');\n const day = String(d.getUTCDate()).padStart(2, '0');\n return `${y}-${m}-${day}`;\n}\n\nexport class PgvectorRecallTracker implements RecallTracker {\n constructor(\n private readonly pool: Pool,\n private readonly table: string = RECALL_TABLE\n ) {}\n\n async migrate(): Promise<void> {\n // [recall-tracking] debug: create table + indexes if missing\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${this.table} (\n id BIGSERIAL PRIMARY KEY,\n agent_id TEXT NOT NULL,\n memory_id TEXT NOT NULL,\n memory_path TEXT NOT NULL,\n query TEXT NOT NULL,\n query_hash TEXT NOT NULL,\n score DOUBLE PRECISION NOT NULL,\n day_bucket TEXT NOT NULL,\n recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n )\n `);\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_agent_day_idx ON ${this.table} (agent_id, day_bucket)`\n );\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_memory_idx ON ${this.table} (agent_id, memory_id)`\n );\n await this.pool.query(\n `CREATE UNIQUE INDEX IF NOT EXISTS ${this.table}_dedupe_idx\n ON ${this.table} (agent_id, memory_id, query_hash, day_bucket)`\n );\n }\n\n async record(params: RecallRecordParams): Promise<void> {\n if (!params.agentId || !params.query.trim() || params.hits.length === 0)\n return;\n const nowMs = params.nowMs ?? Date.now();\n const qhash = hashQuery(params.query);\n const bucket = dayBucket(nowMs);\n\n // [recall-tracking] debug: upsert one row per (agent, memory, query, day)\n // Upstream dedupes per-day per-query so repeated searches don't inflate counts.\n const values: string[] = [];\n const args: unknown[] = [];\n let i = 1;\n for (const hit of params.hits) {\n values.push(\n `($${i++}, $${i++}, $${i++}, $${i++}, $${i++}, $${i++}, $${i++}, NOW())`\n );\n args.push(\n params.agentId,\n hit.id,\n hit.path,\n params.query,\n qhash,\n hit.score,\n bucket\n );\n }\n const sql = `\n INSERT INTO ${this.table}\n (agent_id, memory_id, memory_path, query, query_hash, score, day_bucket, recorded_at)\n VALUES ${values.join(', ')}\n ON CONFLICT (agent_id, memory_id, query_hash, day_bucket) DO UPDATE\n SET score = GREATEST(${this.table}.score, EXCLUDED.score),\n recorded_at = NOW()\n `;\n await this.pool.query(sql, args);\n }\n}\n\n/** No-op tracker — used when recall tracking is disabled or the backend isn't pgvector. */\nexport class NullRecallTracker implements RecallTracker {\n async record(): Promise<void> {}\n async migrate(): Promise<void> {}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Temporal decay — Phase 2.
|
|
3
3
|
*
|
|
4
|
-
* Ported from
|
|
4
|
+
* Ported from upstream `extensions/memory-core/src/memory/temporal-decay.ts`.
|
|
5
5
|
* Ages dated memory files (`memory/YYYY-MM-DD.md`) using exponential decay
|
|
6
6
|
* `multiplier = exp(-ln(2) / halfLifeDays * ageInDays)`. At half-life, the
|
|
7
7
|
* score is exactly halved.
|
|
8
8
|
*
|
|
9
9
|
* Evergreen files (MEMORY.md, memory/topics.md, any non-dated file inside
|
|
10
10
|
* memory/) do NOT decay — they represent durable knowledge and should stay
|
|
11
|
-
* hot regardless of age. This mirrors
|
|
11
|
+
* hot regardless of age. This mirrors upstream's `isEvergreenMemoryPath`.
|
|
12
12
|
*
|
|
13
13
|
* Since our pgvector rows carry `createdAt`, we don't need filesystem stat
|
|
14
14
|
* fallback — the row timestamp is authoritative for any file without a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temporalDecay.mjs","sources":["../../../src/memory/temporalDecay.ts"],"sourcesContent":["/**\n * Temporal decay — Phase 2.\n *\n * Ported from
|
|
1
|
+
{"version":3,"file":"temporalDecay.mjs","sources":["../../../src/memory/temporalDecay.ts"],"sourcesContent":["/**\n * Temporal decay — Phase 2.\n *\n * Ported from upstream `extensions/memory-core/src/memory/temporal-decay.ts`.\n * Ages dated memory files (`memory/YYYY-MM-DD.md`) using exponential decay\n * `multiplier = exp(-ln(2) / halfLifeDays * ageInDays)`. At half-life, the\n * score is exactly halved.\n *\n * Evergreen files (MEMORY.md, memory/topics.md, any non-dated file inside\n * memory/) do NOT decay — they represent durable knowledge and should stay\n * hot regardless of age. This mirrors upstream's `isEvergreenMemoryPath`.\n *\n * Since our pgvector rows carry `createdAt`, we don't need filesystem stat\n * fallback — the row timestamp is authoritative for any file without a\n * date in the path.\n */\n\nexport interface TemporalDecayConfig {\n enabled: boolean;\n halfLifeDays: number;\n}\n\nexport const DEFAULT_TEMPORAL_DECAY_CONFIG: TemporalDecayConfig = {\n enabled: false,\n halfLifeDays: 30,\n};\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst DATED_MEMORY_PATH_RE = /(?:^|\\/)memory\\/(\\d{4})-(\\d{2})-(\\d{2})\\.md$/;\n\nexport function toDecayLambda(halfLifeDays: number): number {\n if (!Number.isFinite(halfLifeDays) || halfLifeDays <= 0) return 0;\n return Math.LN2 / halfLifeDays;\n}\n\nexport function calculateTemporalDecayMultiplier(params: {\n ageInDays: number;\n halfLifeDays: number;\n}): number {\n const lambda = toDecayLambda(params.halfLifeDays);\n const age = Math.max(0, params.ageInDays);\n if (lambda <= 0 || !Number.isFinite(age)) return 1;\n return Math.exp(-lambda * age);\n}\n\nexport function applyTemporalDecayToScore(params: {\n score: number;\n ageInDays: number;\n halfLifeDays: number;\n}): number {\n return params.score * calculateTemporalDecayMultiplier(params);\n}\n\nfunction normalizePath(p: string): string {\n return (p ?? '').replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n}\n\n/** Parse a date out of `memory/YYYY-MM-DD.md` — returns null on non-match or invalid date. */\nexport function parseMemoryDateFromPath(filePath: string): Date | null {\n const m = DATED_MEMORY_PATH_RE.exec(normalizePath(filePath));\n if (!m) return null;\n const y = Number(m[1]);\n const mo = Number(m[2]);\n const d = Number(m[3]);\n if (!Number.isInteger(y) || !Number.isInteger(mo) || !Number.isInteger(d))\n return null;\n const ts = Date.UTC(y, mo - 1, d);\n const parsed = new Date(ts);\n if (\n parsed.getUTCFullYear() !== y ||\n parsed.getUTCMonth() !== mo - 1 ||\n parsed.getUTCDate() !== d\n ) {\n return null;\n }\n return parsed;\n}\n\n/**\n * Evergreen = durable knowledge file that should not decay.\n * - `MEMORY.md` / `memory.md` at root\n * - anything inside `memory/` that is NOT a dated `YYYY-MM-DD.md` file\n */\nexport function isEvergreenMemoryPath(filePath: string): boolean {\n const n = normalizePath(filePath);\n if (n === 'MEMORY.md' || n === 'memory.md') return true;\n if (!n.startsWith('memory/')) return false;\n return !DATED_MEMORY_PATH_RE.test(n);\n}\n\nfunction ageInDays(timestamp: Date, nowMs: number): number {\n return Math.max(0, nowMs - timestamp.getTime()) / DAY_MS;\n}\n\nexport interface DecayCandidate {\n path: string;\n score: number;\n createdAt?: Date;\n}\n\n/**\n * Apply temporal decay to a list of memory hits.\n *\n * Priority for the effective timestamp:\n * 1. Dated path (`memory/YYYY-MM-DD.md`) — use the date in the path\n * 2. Otherwise, if the path is evergreen — NO decay\n * 3. Otherwise, use the row's `createdAt`\n */\nexport function applyTemporalDecayToHits<T extends DecayCandidate>(\n hits: T[],\n config: Partial<TemporalDecayConfig> = {},\n nowMs: number = Date.now()\n): T[] {\n const merged = { ...DEFAULT_TEMPORAL_DECAY_CONFIG, ...config };\n if (!merged.enabled) return [...hits];\n\n return hits.map((h) => {\n const datedTs = parseMemoryDateFromPath(h.path);\n let ts: Date | null = datedTs;\n if (!ts) {\n if (isEvergreenMemoryPath(h.path)) return h;\n ts = h.createdAt ?? null;\n }\n if (!ts) return h;\n return {\n ...h,\n score: applyTemporalDecayToScore({\n score: h.score,\n ageInDays: ageInDays(ts, nowMs),\n halfLifeDays: merged.halfLifeDays,\n }),\n };\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,47 +1,6 @@
|
|
|
1
1
|
import { BaseMessage, ToolMessage, SystemMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
|
|
2
2
|
import { ContentTypes } from '../common/enum.mjs';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Strips Anthropic-style `cache_control` markers from every block in the
|
|
6
|
-
* given content array. Returns the (possibly cloned) content and a flag
|
|
7
|
-
* indicating whether anything was actually modified — so callers can avoid
|
|
8
|
-
* unnecessary message clones.
|
|
9
|
-
*
|
|
10
|
-
* Used to sanitize a system message's content array before adding Bedrock
|
|
11
|
-
* cache points, so stray Anthropic markers (e.g. left over from a previous
|
|
12
|
-
* Anthropic-provider turn) don't prevent the Bedrock provider from caching
|
|
13
|
-
* the system prefix.
|
|
14
|
-
*/
|
|
15
|
-
function stripAnthropicCacheControlFromBlocks(content) {
|
|
16
|
-
let modified = false;
|
|
17
|
-
const strippedContent = content.map((block) => {
|
|
18
|
-
if (!('cache_control' in block)) {
|
|
19
|
-
return block;
|
|
20
|
-
}
|
|
21
|
-
const cloned = { ...block };
|
|
22
|
-
delete cloned.cache_control;
|
|
23
|
-
modified = true;
|
|
24
|
-
return cloned;
|
|
25
|
-
});
|
|
26
|
-
return { content: strippedContent, modified };
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* For the Bedrock cache pass, system messages must NOT carry Anthropic
|
|
30
|
-
* cache_control markers (those are an Anthropic-specific encoding and
|
|
31
|
-
* Bedrock uses cachePoint blocks instead). Strip them before the cache
|
|
32
|
-
* point insertion logic walks the message.
|
|
33
|
-
*/
|
|
34
|
-
function sanitizeBedrockSystemMessage(message) {
|
|
35
|
-
const content = message.content;
|
|
36
|
-
if (!Array.isArray(content)) {
|
|
37
|
-
return message;
|
|
38
|
-
}
|
|
39
|
-
const stripped = stripAnthropicCacheControlFromBlocks(content);
|
|
40
|
-
if (!stripped.modified) {
|
|
41
|
-
return message;
|
|
42
|
-
}
|
|
43
|
-
return cloneMessage(message, stripped.content);
|
|
44
|
-
}
|
|
45
4
|
/** Debug logger for cache operations - set ILLUMA_DEBUG_CACHE=true to enable */
|
|
46
5
|
const debugCache = (message, data) => {
|
|
47
6
|
if (process.env.ILLUMA_DEBUG_CACHE === 'true') {
|
|
@@ -309,33 +268,8 @@ function addBedrockCacheControl(messages) {
|
|
|
309
268
|
});
|
|
310
269
|
// Clone messages to avoid mutating originals
|
|
311
270
|
const updatedMessages = messages.map((msg) => {
|
|
312
|
-
/* PRESERVE cachePoint on system messages — they're added inline by
|
|
313
|
-
* AgentContext.buildSystemRunnable to mark the cacheable system prefix
|
|
314
|
-
* for account-level caching. Only strip stray Anthropic cache_control
|
|
315
|
-
* (which Bedrock ignores). For non-system messages keep the existing
|
|
316
|
-
* behaviour: full strip + repopulate based on last-2 strategy. */
|
|
317
|
-
const msgType = 'getType' in msg && typeof msg.getType === 'function'
|
|
318
|
-
? msg.getType()
|
|
319
|
-
: undefined;
|
|
320
|
-
const msgRole = msg.role;
|
|
321
|
-
const isSystem = msgType === 'system' || msgRole === 'system';
|
|
322
271
|
const content = msg.content;
|
|
323
272
|
if (Array.isArray(content)) {
|
|
324
|
-
if (isSystem) {
|
|
325
|
-
/* System: keep cachePoint, only strip Anthropic cache_control. */
|
|
326
|
-
if (hasAnthropicCacheControl(content)) {
|
|
327
|
-
const stripped = content.map((block) => {
|
|
328
|
-
const rec = block;
|
|
329
|
-
if ('cache_control' in rec) {
|
|
330
|
-
const { cache_control: _, ...rest } = rec;
|
|
331
|
-
return rest;
|
|
332
|
-
}
|
|
333
|
-
return block;
|
|
334
|
-
});
|
|
335
|
-
return cloneMessage(msg, stripped);
|
|
336
|
-
}
|
|
337
|
-
return cloneMessage(msg, content);
|
|
338
|
-
}
|
|
339
273
|
// Strip existing cachePoint blocks and Anthropic-style cache_control
|
|
340
274
|
const stripped = content
|
|
341
275
|
.filter((block) => !isCachePoint(block))
|
|
@@ -415,29 +349,6 @@ function addBedrockCacheControl(messages) {
|
|
|
415
349
|
for (let i = updatedMessages.length - 1; i >= 0 && applied < MAX_CACHE_POINTS; i--) {
|
|
416
350
|
const message = updatedMessages[i];
|
|
417
351
|
const msgContent = message.content;
|
|
418
|
-
/* System messages are handled by AgentContext.buildSystemRunnable —
|
|
419
|
-
* the cachePoint is emitted INLINE in the system message content array
|
|
420
|
-
* so the cacheable system prefix gets a marker every turn (not just
|
|
421
|
-
* when the system message happens to be in the last 2 eligible). The
|
|
422
|
-
* Bedrock converter (src/llm/bedrock/utils/message_inputs.ts:261-294)
|
|
423
|
-
* passes through cachePoint blocks in system content arrays.
|
|
424
|
-
*
|
|
425
|
-
* If we ALSO marked system here, we'd: (a) waste one of the two
|
|
426
|
-
* cachePoint budget slots that Bedrock honours per-request, starving
|
|
427
|
-
* the conversation tail, and (b) potentially double-mark a stable
|
|
428
|
-
* prefix that already has its own cachePoint.
|
|
429
|
-
*
|
|
430
|
-
* As a defensive cleanup, strip stray Anthropic-style cache_control
|
|
431
|
-
* markers from the system message — Bedrock ignores them and they
|
|
432
|
-
* can confuse provider validation. */
|
|
433
|
-
const _msgType = 'getType' in message && typeof message.getType === 'function'
|
|
434
|
-
? message.getType()
|
|
435
|
-
: undefined;
|
|
436
|
-
const _msgRole = message.role;
|
|
437
|
-
if (_msgType === 'system' || _msgRole === 'system') {
|
|
438
|
-
updatedMessages[i] = sanitizeBedrockSystemMessage(message);
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
352
|
// Skip empty/whitespace-only content
|
|
442
353
|
if (msgContent == null)
|
|
443
354
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.mjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import {\n AIMessage,\n BaseMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport type { AnthropicMessage } from '@/types/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { ContentTypes } from '@/common/enum';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\ntype MessageContentWithCacheControl = MessageContentComplex & {\n cache_control?: unknown;\n};\n\n/**\n * Strips Anthropic-style `cache_control` markers from every block in the\n * given content array. Returns the (possibly cloned) content and a flag\n * indicating whether anything was actually modified — so callers can avoid\n * unnecessary message clones.\n *\n * Used to sanitize a system message's content array before adding Bedrock\n * cache points, so stray Anthropic markers (e.g. left over from a previous\n * Anthropic-provider turn) don't prevent the Bedrock provider from caching\n * the system prefix.\n */\nfunction stripAnthropicCacheControlFromBlocks(\n content: MessageContentComplex[]\n): { content: MessageContentComplex[]; modified: boolean } {\n let modified = false;\n const strippedContent = content.map((block) => {\n if (!('cache_control' in block)) {\n return block;\n }\n\n const cloned: MessageContentWithCacheControl = { ...block };\n delete cloned.cache_control;\n modified = true;\n return cloned;\n });\n\n return { content: strippedContent, modified };\n}\n\n/**\n * For the Bedrock cache pass, system messages must NOT carry Anthropic\n * cache_control markers (those are an Anthropic-specific encoding and\n * Bedrock uses cachePoint blocks instead). Strip them before the cache\n * point insertion logic walks the message.\n */\nfunction sanitizeBedrockSystemMessage<T extends MessageWithContent>(\n message: T\n): T {\n const content = message.content;\n if (!Array.isArray(content)) {\n return message;\n }\n\n const stripped = stripAnthropicCacheControlFromBlocks(content);\n if (!stripped.modified) {\n return message;\n }\n\n return cloneMessage(message, stripped.content);\n}\n\n/** Debug logger for cache operations - set ILLUMA_DEBUG_CACHE=true to enable */\nconst debugCache = (message: string, data?: unknown): void => {\n if (process.env.ILLUMA_DEBUG_CACHE === 'true') {\n // eslint-disable-next-line no-console\n console.log(\n `[Cache] ${message}`,\n data !== undefined ? JSON.stringify(data, null, 2) : ''\n );\n }\n};\n\n/**\n * Deep clones a message's content to prevent mutation of the original.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Clones a message with new content. For LangChain BaseMessage instances,\n * constructs a proper class instance so that `instanceof` checks are preserved\n * in downstream code (e.g., ensureThinkingBlockInMessages).\n * For plain objects (AnthropicMessage), uses object spread.\n */\nfunction cloneMessage<T extends MessageWithContent>(\n message: T,\n content: string | MessageContentComplex[]\n): T {\n if (message instanceof BaseMessage) {\n const baseParams = {\n content,\n additional_kwargs: { ...message.additional_kwargs },\n response_metadata: { ...message.response_metadata },\n id: message.id,\n name: message.name,\n };\n\n const msgType = message.getType();\n switch (msgType) {\n case 'ai':\n return new AIMessage({\n ...baseParams,\n tool_calls: (message as unknown as AIMessage).tool_calls,\n }) as unknown as T;\n case 'human':\n return new HumanMessage(baseParams) as unknown as T;\n case 'system':\n return new SystemMessage(baseParams) as unknown as T;\n case 'tool':\n return new ToolMessage({\n ...baseParams,\n tool_call_id: (message as unknown as ToolMessage).tool_call_id,\n }) as unknown as T;\n default:\n break;\n }\n }\n\n const {\n lc_kwargs: _lc_kwargs,\n lc_serializable: _lc_serializable,\n lc_namespace: _lc_namespace,\n ...rest\n } = message as T & {\n lc_kwargs?: unknown;\n lc_serializable?: unknown;\n lc_namespace?: unknown;\n };\n\n const cloned = { ...rest, content } as T;\n\n // Sync lc_kwargs.content with the new content to prevent LangChain coercion issues\n const lcKwargs = (message as Record<string, unknown>).lc_kwargs as\n | Record<string, unknown>\n | undefined;\n if (lcKwargs != null) {\n (cloned as Record<string, unknown>).lc_kwargs = {\n ...lcKwargs,\n content: content,\n };\n }\n\n // LangChain messages don't have a direct 'role' property - derive it from getType()\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n !('role' in cloned)\n ) {\n const msgType = (message as unknown as BaseMessage).getType();\n const roleMap: Record<string, string> = {\n human: 'user',\n ai: 'assistant',\n system: 'system',\n tool: 'tool',\n };\n (cloned as Record<string, unknown>).role = roleMap[msgType] || msgType;\n }\n\n return cloned;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Checks if a message's content needs cache control stripping.\n * Returns true if content has cachePoint blocks or cache_control fields.\n */\nfunction needsCacheStripping(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n const block = content[i];\n if (isCachePoint(block)) return true;\n if ('cache_control' in block) return true;\n }\n return false;\n}\n\n/**\n * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n\n const hasArrayContent = Array.isArray(content);\n const needsStripping =\n hasArrayContent &&\n needsCacheStripping(content as MessageContentComplex[]);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsStripping && !needsCacheAdd) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n\n if (hasArrayContent) {\n workingContent = deepCloneContent(\n content as MessageContentComplex[]\n ).filter((block) => !isCachePoint(block as MessageContentComplex));\n\n for (let j = 0; j < workingContent.length; j++) {\n const block = workingContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n } else if (typeof content === 'string') {\n workingContent = [\n { type: 'text', text: content },\n ] as MessageContentComplex[];\n } else {\n workingContent = [];\n }\n\n if (userMessagesModified >= 2 || !isUserMessage) {\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n continue;\n }\n\n for (let j = workingContent.length - 1; j >= 0; j--) {\n const contentPart = workingContent[j];\n if ('type' in contentPart && contentPart.type === 'text') {\n (contentPart as Anthropic.TextBlockParam).cache_control = {\n type: 'ephemeral',\n };\n userMessagesModified++;\n break;\n }\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content);\n for (let j = 0; j < clonedContent.length; j++) {\n const block = clonedContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n );\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points using \"Stable Prefix Caching\" strategy.\n *\n * STRATEGY: Place cache point after the LAST ASSISTANT message only.\n * This ensures the prefix (everything before the cache point) remains STABLE\n * as the conversation grows, maximizing cache hits.\n *\n * Why this works:\n * - System message has its own cachePoint (added in AgentContext)\n * - Tools have their own cachePoint (added in IllumaBedrockConverse)\n * - Conversation history grows, but the PREFIX stays the same\n * - Only the NEW user message is uncached (it's always different)\n *\n * Example conversation flow:\n * Request 1: [System+cachePoint][Tools+cachePoint][User1] → No conversation cache yet\n * Request 2: [System][Tools][User1][Assistant1+cachePoint][User2] → Cache User1+Assistant1\n * Request 3: [System][Tools][User1][Assistant1][User2][Assistant2+cachePoint][User3]\n * → Cache reads User1+A1+User2+A2, cache writes new portion\n *\n * Claude's \"Simplified Cache Management\" automatically looks back up to 20 content\n * blocks from the cache checkpoint to find the longest matching prefix.\n *\n * @param messages - The array of message objects (excluding system message).\n * @returns - The updated array with a single cache point after the last assistant message.\n */\nexport function addBedrockCacheControl<\n T extends Partial<BaseMessage> & MessageWithContent,\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length < 1) {\n debugCache('addBedrockCacheControl: Skipping - no messages', {\n count: messages.length,\n });\n return messages;\n }\n\n debugCache(\n 'addBedrockCacheControl: Processing messages with stable prefix strategy',\n {\n count: messages.length,\n }\n );\n\n // Clone messages to avoid mutating originals\n const updatedMessages: T[] = messages.map((msg) => {\n /* PRESERVE cachePoint on system messages — they're added inline by\n * AgentContext.buildSystemRunnable to mark the cacheable system prefix\n * for account-level caching. Only strip stray Anthropic cache_control\n * (which Bedrock ignores). For non-system messages keep the existing\n * behaviour: full strip + repopulate based on last-2 strategy. */\n const msgType =\n 'getType' in msg && typeof msg.getType === 'function'\n ? msg.getType()\n : undefined;\n const msgRole = (msg as Record<string, unknown>).role as string | undefined;\n const isSystem = msgType === 'system' || msgRole === 'system';\n\n const content = msg.content;\n if (Array.isArray(content)) {\n if (isSystem) {\n /* System: keep cachePoint, only strip Anthropic cache_control. */\n if (hasAnthropicCacheControl(content)) {\n const stripped = content.map((block) => {\n const rec = block as Record<string, unknown>;\n if ('cache_control' in rec) {\n const { cache_control: _, ...rest } = rec;\n return rest as MessageContentComplex;\n }\n return block;\n });\n return cloneMessage(msg, stripped) as T;\n }\n return cloneMessage(msg, content) as T;\n }\n\n // Strip existing cachePoint blocks and Anthropic-style cache_control\n const stripped = content\n .filter((block) => !isCachePoint(block))\n .map((block) => {\n const rec = block as Record<string, unknown>;\n if ('cache_control' in rec) {\n const { cache_control: _, ...rest } = rec;\n return rest as MessageContentComplex;\n }\n return block;\n });\n if (needsCacheStripping(content) || hasAnthropicCacheControl(content)) {\n return cloneMessage(msg, stripped) as T;\n }\n }\n return cloneMessage(msg, content as string | MessageContentComplex[]) as T;\n });\n\n // Helper function to check if a message contains reasoning/thinking blocks\n const hasReasoningBlock = (message: T): boolean => {\n const content = message.content;\n if (!Array.isArray(content)) {\n return false;\n }\n for (const block of content) {\n const type = (block as { type?: string }).type;\n if (\n type === 'reasoning_content' ||\n type === 'reasoning' ||\n type === 'thinking' ||\n type === 'redacted_thinking'\n ) {\n return true;\n }\n }\n return false;\n };\n\n /**\n * Helper to add a cachePoint to a message's content.\n * For strings: wraps into [{type: 'text', text}, {cachePoint}]\n * For arrays: inserts cachePoint after the last non-whitespace text block\n * Returns the new message or null if no suitable insertion point.\n */\n const addCachePointToMessage = (message: T): T | null => {\n const msgContent = message.content;\n\n if (typeof msgContent === 'string' && msgContent !== '') {\n const newContent = [\n { type: ContentTypes.TEXT, text: msgContent },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n return cloneMessage(message, newContent) as T;\n }\n\n if (Array.isArray(msgContent) && msgContent.length > 0) {\n if (hasReasoningBlock(message)) {\n return null;\n }\n // Find the last text block and insert cache point after it\n for (let j = msgContent.length - 1; j >= 0; j--) {\n const type = (msgContent[j] as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (msgContent[j] as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n const newContent = [\n ...msgContent.slice(0, j + 1),\n { cachePoint: { type: 'default' } } as MessageContentComplex,\n ...msgContent.slice(j + 1),\n ];\n return cloneMessage(message, newContent) as T;\n }\n }\n }\n }\n\n return null;\n };\n\n // Add cache points to the last 2 messages (from the end) that have eligible content.\n // This mirrors Anthropic's two-breakpoint strategy for Bedrock's cache API.\n // Skip messages with only whitespace, empty content, or reasoning blocks.\n const MAX_CACHE_POINTS = 2;\n let applied = 0;\n\n for (\n let i = updatedMessages.length - 1;\n i >= 0 && applied < MAX_CACHE_POINTS;\n i--\n ) {\n const message = updatedMessages[i];\n const msgContent = message.content;\n\n /* System messages are handled by AgentContext.buildSystemRunnable —\n * the cachePoint is emitted INLINE in the system message content array\n * so the cacheable system prefix gets a marker every turn (not just\n * when the system message happens to be in the last 2 eligible). The\n * Bedrock converter (src/llm/bedrock/utils/message_inputs.ts:261-294)\n * passes through cachePoint blocks in system content arrays.\n *\n * If we ALSO marked system here, we'd: (a) waste one of the two\n * cachePoint budget slots that Bedrock honours per-request, starving\n * the conversation tail, and (b) potentially double-mark a stable\n * prefix that already has its own cachePoint.\n *\n * As a defensive cleanup, strip stray Anthropic-style cache_control\n * markers from the system message — Bedrock ignores them and they\n * can confuse provider validation. */\n const _msgType =\n 'getType' in message && typeof message.getType === 'function'\n ? message.getType()\n : undefined;\n const _msgRole = (message as Record<string, unknown>).role as\n | string\n | undefined;\n if (_msgType === 'system' || _msgRole === 'system') {\n updatedMessages[i] = sanitizeBedrockSystemMessage(\n message as MessageWithContent\n ) as T;\n continue;\n }\n\n // Skip empty/whitespace-only content\n if (msgContent == null) continue;\n if (typeof msgContent === 'string' && msgContent.trim() === '') continue;\n if (Array.isArray(msgContent) && msgContent.length === 0) continue;\n\n // Skip non-string, non-array content (e.g., number, object without type)\n if (typeof msgContent !== 'string' && !Array.isArray(msgContent)) continue;\n\n // Skip AI messages with only whitespace text and reasoning blocks (tool-call scenario)\n const messageType =\n 'getType' in message && typeof message.getType === 'function'\n ? message.getType()\n : 'unknown';\n const role = (message as Record<string, unknown>).role as\n | string\n | undefined;\n const isAi = messageType === 'ai' || role === 'assistant';\n if (isAi && hasReasoningBlock(message)) {\n // Check if all text blocks are whitespace-only\n if (Array.isArray(msgContent)) {\n const hasNonWhitespaceText = msgContent.some((block) => {\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n return text != null && text.trim() !== '';\n }\n return false;\n });\n if (!hasNonWhitespaceText) {\n debugCache(\n `⚠️ Message cachePoint SKIPPED at index ${i} (AI with whitespace-only text + reasoning)`\n );\n continue;\n }\n }\n }\n\n // Skip messages that already have cache points (multi-agent scenarios)\n if (\n Array.isArray(msgContent) &&\n msgContent.some((b) => isCachePoint(b as MessageContentComplex))\n ) {\n continue;\n }\n\n const updated = addCachePointToMessage(message);\n if (updated != null) {\n updatedMessages[i] = updated;\n applied++;\n debugCache(\n `📍 Message cachePoint at index ${i} (${typeof message.content === 'string' ? 'string' : 'array'})`\n );\n }\n }\n\n debugCache(\n 'addBedrockCacheControl: Complete - stable prefix caching applied',\n {\n appliedCachePoints: applied,\n totalMessages: updatedMessages.length,\n }\n );\n\n return updatedMessages;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache.mjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import {\n AIMessage,\n BaseMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport type { AnthropicMessage } from '@/types/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { ContentTypes } from '@/common/enum';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\n/** Debug logger for cache operations - set ILLUMA_DEBUG_CACHE=true to enable */\nconst debugCache = (message: string, data?: unknown): void => {\n if (process.env.ILLUMA_DEBUG_CACHE === 'true') {\n // eslint-disable-next-line no-console\n console.log(\n `[Cache] ${message}`,\n data !== undefined ? JSON.stringify(data, null, 2) : ''\n );\n }\n};\n\n/**\n * Deep clones a message's content to prevent mutation of the original.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Clones a message with new content. For LangChain BaseMessage instances,\n * constructs a proper class instance so that `instanceof` checks are preserved\n * in downstream code (e.g., ensureThinkingBlockInMessages).\n * For plain objects (AnthropicMessage), uses object spread.\n */\nfunction cloneMessage<T extends MessageWithContent>(\n message: T,\n content: string | MessageContentComplex[]\n): T {\n if (message instanceof BaseMessage) {\n const baseParams = {\n content,\n additional_kwargs: { ...message.additional_kwargs },\n response_metadata: { ...message.response_metadata },\n id: message.id,\n name: message.name,\n };\n\n const msgType = message.getType();\n switch (msgType) {\n case 'ai':\n return new AIMessage({\n ...baseParams,\n tool_calls: (message as unknown as AIMessage).tool_calls,\n }) as unknown as T;\n case 'human':\n return new HumanMessage(baseParams) as unknown as T;\n case 'system':\n return new SystemMessage(baseParams) as unknown as T;\n case 'tool':\n return new ToolMessage({\n ...baseParams,\n tool_call_id: (message as unknown as ToolMessage).tool_call_id,\n }) as unknown as T;\n default:\n break;\n }\n }\n\n const {\n lc_kwargs: _lc_kwargs,\n lc_serializable: _lc_serializable,\n lc_namespace: _lc_namespace,\n ...rest\n } = message as T & {\n lc_kwargs?: unknown;\n lc_serializable?: unknown;\n lc_namespace?: unknown;\n };\n\n const cloned = { ...rest, content } as T;\n\n // Sync lc_kwargs.content with the new content to prevent LangChain coercion issues\n const lcKwargs = (message as Record<string, unknown>).lc_kwargs as\n | Record<string, unknown>\n | undefined;\n if (lcKwargs != null) {\n (cloned as Record<string, unknown>).lc_kwargs = {\n ...lcKwargs,\n content: content,\n };\n }\n\n // LangChain messages don't have a direct 'role' property - derive it from getType()\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n !('role' in cloned)\n ) {\n const msgType = (message as unknown as BaseMessage).getType();\n const roleMap: Record<string, string> = {\n human: 'user',\n ai: 'assistant',\n system: 'system',\n tool: 'tool',\n };\n (cloned as Record<string, unknown>).role = roleMap[msgType] || msgType;\n }\n\n return cloned;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Checks if a message's content needs cache control stripping.\n * Returns true if content has cachePoint blocks or cache_control fields.\n */\nfunction needsCacheStripping(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n const block = content[i];\n if (isCachePoint(block)) return true;\n if ('cache_control' in block) return true;\n }\n return false;\n}\n\n/**\n * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n\n const hasArrayContent = Array.isArray(content);\n const needsStripping =\n hasArrayContent &&\n needsCacheStripping(content as MessageContentComplex[]);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsStripping && !needsCacheAdd) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n\n if (hasArrayContent) {\n workingContent = deepCloneContent(\n content as MessageContentComplex[]\n ).filter((block) => !isCachePoint(block as MessageContentComplex));\n\n for (let j = 0; j < workingContent.length; j++) {\n const block = workingContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n } else if (typeof content === 'string') {\n workingContent = [\n { type: 'text', text: content },\n ] as MessageContentComplex[];\n } else {\n workingContent = [];\n }\n\n if (userMessagesModified >= 2 || !isUserMessage) {\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n continue;\n }\n\n for (let j = workingContent.length - 1; j >= 0; j--) {\n const contentPart = workingContent[j];\n if ('type' in contentPart && contentPart.type === 'text') {\n (contentPart as Anthropic.TextBlockParam).cache_control = {\n type: 'ephemeral',\n };\n userMessagesModified++;\n break;\n }\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content);\n for (let j = 0; j < clonedContent.length; j++) {\n const block = clonedContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n );\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points using \"Stable Prefix Caching\" strategy.\n *\n * STRATEGY: Place cache point after the LAST ASSISTANT message only.\n * This ensures the prefix (everything before the cache point) remains STABLE\n * as the conversation grows, maximizing cache hits.\n *\n * Why this works:\n * - System message has its own cachePoint (added in AgentContext)\n * - Tools have their own cachePoint (added in IllumaBedrockConverse)\n * - Conversation history grows, but the PREFIX stays the same\n * - Only the NEW user message is uncached (it's always different)\n *\n * Example conversation flow:\n * Request 1: [System+cachePoint][Tools+cachePoint][User1] → No conversation cache yet\n * Request 2: [System][Tools][User1][Assistant1+cachePoint][User2] → Cache User1+Assistant1\n * Request 3: [System][Tools][User1][Assistant1][User2][Assistant2+cachePoint][User3]\n * → Cache reads User1+A1+User2+A2, cache writes new portion\n *\n * Claude's \"Simplified Cache Management\" automatically looks back up to 20 content\n * blocks from the cache checkpoint to find the longest matching prefix.\n *\n * @param messages - The array of message objects (excluding system message).\n * @returns - The updated array with a single cache point after the last assistant message.\n */\nexport function addBedrockCacheControl<\n T extends Partial<BaseMessage> & MessageWithContent,\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length < 1) {\n debugCache('addBedrockCacheControl: Skipping - no messages', {\n count: messages.length,\n });\n return messages;\n }\n\n debugCache(\n 'addBedrockCacheControl: Processing messages with stable prefix strategy',\n {\n count: messages.length,\n }\n );\n\n // Clone messages to avoid mutating originals\n const updatedMessages: T[] = messages.map((msg) => {\n const content = msg.content;\n if (Array.isArray(content)) {\n // Strip existing cachePoint blocks and Anthropic-style cache_control\n const stripped = content\n .filter((block) => !isCachePoint(block))\n .map((block) => {\n const rec = block as Record<string, unknown>;\n if ('cache_control' in rec) {\n const { cache_control: _, ...rest } = rec;\n return rest as MessageContentComplex;\n }\n return block;\n });\n if (needsCacheStripping(content) || hasAnthropicCacheControl(content)) {\n return cloneMessage(msg, stripped) as T;\n }\n }\n return cloneMessage(msg, content as string | MessageContentComplex[]) as T;\n });\n\n // Helper function to check if a message contains reasoning/thinking blocks\n const hasReasoningBlock = (message: T): boolean => {\n const content = message.content;\n if (!Array.isArray(content)) {\n return false;\n }\n for (const block of content) {\n const type = (block as { type?: string }).type;\n if (\n type === 'reasoning_content' ||\n type === 'reasoning' ||\n type === 'thinking' ||\n type === 'redacted_thinking'\n ) {\n return true;\n }\n }\n return false;\n };\n\n /**\n * Helper to add a cachePoint to a message's content.\n * For strings: wraps into [{type: 'text', text}, {cachePoint}]\n * For arrays: inserts cachePoint after the last non-whitespace text block\n * Returns the new message or null if no suitable insertion point.\n */\n const addCachePointToMessage = (message: T): T | null => {\n const msgContent = message.content;\n\n if (typeof msgContent === 'string' && msgContent !== '') {\n const newContent = [\n { type: ContentTypes.TEXT, text: msgContent },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n return cloneMessage(message, newContent) as T;\n }\n\n if (Array.isArray(msgContent) && msgContent.length > 0) {\n if (hasReasoningBlock(message)) {\n return null;\n }\n // Find the last text block and insert cache point after it\n for (let j = msgContent.length - 1; j >= 0; j--) {\n const type = (msgContent[j] as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (msgContent[j] as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n const newContent = [\n ...msgContent.slice(0, j + 1),\n { cachePoint: { type: 'default' } } as MessageContentComplex,\n ...msgContent.slice(j + 1),\n ];\n return cloneMessage(message, newContent) as T;\n }\n }\n }\n }\n\n return null;\n };\n\n // Add cache points to the last 2 messages (from the end) that have eligible content.\n // This mirrors Anthropic's two-breakpoint strategy for Bedrock's cache API.\n // Skip messages with only whitespace, empty content, or reasoning blocks.\n const MAX_CACHE_POINTS = 2;\n let applied = 0;\n\n for (\n let i = updatedMessages.length - 1;\n i >= 0 && applied < MAX_CACHE_POINTS;\n i--\n ) {\n const message = updatedMessages[i];\n const msgContent = message.content;\n\n // Skip empty/whitespace-only content\n if (msgContent == null) continue;\n if (typeof msgContent === 'string' && msgContent.trim() === '') continue;\n if (Array.isArray(msgContent) && msgContent.length === 0) continue;\n\n // Skip non-string, non-array content (e.g., number, object without type)\n if (typeof msgContent !== 'string' && !Array.isArray(msgContent)) continue;\n\n // Skip AI messages with only whitespace text and reasoning blocks (tool-call scenario)\n const messageType =\n 'getType' in message && typeof message.getType === 'function'\n ? message.getType()\n : 'unknown';\n const role = (message as Record<string, unknown>).role as\n | string\n | undefined;\n const isAi = messageType === 'ai' || role === 'assistant';\n if (isAi && hasReasoningBlock(message)) {\n // Check if all text blocks are whitespace-only\n if (Array.isArray(msgContent)) {\n const hasNonWhitespaceText = msgContent.some((block) => {\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n return text != null && text.trim() !== '';\n }\n return false;\n });\n if (!hasNonWhitespaceText) {\n debugCache(\n `⚠️ Message cachePoint SKIPPED at index ${i} (AI with whitespace-only text + reasoning)`\n );\n continue;\n }\n }\n }\n\n // Skip messages that already have cache points (multi-agent scenarios)\n if (\n Array.isArray(msgContent) &&\n msgContent.some((b) => isCachePoint(b as MessageContentComplex))\n ) {\n continue;\n }\n\n const updated = addCachePointToMessage(message);\n if (updated != null) {\n updatedMessages[i] = updated;\n applied++;\n debugCache(\n `📍 Message cachePoint at index ${i} (${typeof message.content === 'string' ? 'string' : 'array'})`\n );\n }\n }\n\n debugCache(\n 'addBedrockCacheControl: Complete - stable prefix caching applied',\n {\n appliedCachePoints: applied,\n totalMessages: updatedMessages.length,\n }\n );\n\n return updatedMessages;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -149,69 +149,16 @@ const formatFromLangChain = (message) => {
|
|
|
149
149
|
...additional_kwargs,
|
|
150
150
|
};
|
|
151
151
|
};
|
|
152
|
-
function extractReasoningContent(part) {
|
|
153
|
-
if (part == null || typeof part !== 'object')
|
|
154
|
-
return '';
|
|
155
|
-
if (part.type === ContentTypes.THINK) {
|
|
156
|
-
const think = part.think;
|
|
157
|
-
return typeof think === 'string' ? think : '';
|
|
158
|
-
}
|
|
159
|
-
if (part.type === ContentTypes.THINKING) {
|
|
160
|
-
const thinking = part.thinking;
|
|
161
|
-
return typeof thinking === 'string' ? thinking : '';
|
|
162
|
-
}
|
|
163
|
-
if (part.type === ContentTypes.REASONING) {
|
|
164
|
-
const reasoning = part.reasoning;
|
|
165
|
-
return typeof reasoning === 'string' ? reasoning : '';
|
|
166
|
-
}
|
|
167
|
-
if (part.type === ContentTypes.REASONING_CONTENT) {
|
|
168
|
-
/* Bedrock/Google nested shape: { reasoningContent: { text: ... } } */
|
|
169
|
-
const nested = part
|
|
170
|
-
.reasoningContent;
|
|
171
|
-
if (nested && typeof nested.text === 'string')
|
|
172
|
-
return nested.text;
|
|
173
|
-
}
|
|
174
|
-
return '';
|
|
175
|
-
}
|
|
176
152
|
/**
|
|
177
153
|
* Helper function to format an assistant message
|
|
178
154
|
* @param message The message to format
|
|
179
|
-
* @param options Optional formatting options (e.g. DeepSeek reasoning roundtrip)
|
|
180
155
|
* @returns Array of formatted messages
|
|
181
156
|
*/
|
|
182
|
-
function formatAssistantMessage(message
|
|
157
|
+
function formatAssistantMessage(message) {
|
|
183
158
|
const formattedMessages = [];
|
|
184
159
|
let currentContent = [];
|
|
185
160
|
let lastAIMessage = null;
|
|
186
161
|
let hasReasoning = false;
|
|
187
|
-
let pendingReasoningContent = '';
|
|
188
|
-
const shouldPreserveReasoningContent = options?.preserveReasoningContent === true;
|
|
189
|
-
const takePendingReasoningContent = () => {
|
|
190
|
-
if (!shouldPreserveReasoningContent || !pendingReasoningContent) {
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
const reasoningContent = pendingReasoningContent;
|
|
194
|
-
pendingReasoningContent = '';
|
|
195
|
-
return reasoningContent;
|
|
196
|
-
};
|
|
197
|
-
const createAIMessage = (content) => {
|
|
198
|
-
const reasoningContent = takePendingReasoningContent();
|
|
199
|
-
return new AIMessage({
|
|
200
|
-
content,
|
|
201
|
-
...(reasoningContent != null && {
|
|
202
|
-
additional_kwargs: { reasoning_content: reasoningContent },
|
|
203
|
-
}),
|
|
204
|
-
});
|
|
205
|
-
};
|
|
206
|
-
const attachPendingReasoningContent = (aiMessage) => {
|
|
207
|
-
const reasoningContent = takePendingReasoningContent();
|
|
208
|
-
if (reasoningContent == null)
|
|
209
|
-
return;
|
|
210
|
-
aiMessage.additional_kwargs.reasoning_content =
|
|
211
|
-
typeof aiMessage.additional_kwargs.reasoning_content === 'string'
|
|
212
|
-
? `${aiMessage.additional_kwargs.reasoning_content}${reasoningContent}`
|
|
213
|
-
: reasoningContent;
|
|
214
|
-
};
|
|
215
162
|
if (Array.isArray(message.content)) {
|
|
216
163
|
for (const part of message.content) {
|
|
217
164
|
if (part == null) {
|
|
@@ -231,13 +178,15 @@ function formatAssistantMessage(message, options) {
|
|
|
231
178
|
}, '');
|
|
232
179
|
content =
|
|
233
180
|
`${content}\n${part[ContentTypes.TEXT] ?? part.text ?? ''}`.trim();
|
|
234
|
-
lastAIMessage =
|
|
181
|
+
lastAIMessage = new AIMessage({ content });
|
|
235
182
|
formattedMessages.push(lastAIMessage);
|
|
236
183
|
currentContent = [];
|
|
237
184
|
continue;
|
|
238
185
|
}
|
|
239
186
|
// Create a new AIMessage with this text and prepare for tool calls
|
|
240
|
-
lastAIMessage =
|
|
187
|
+
lastAIMessage = new AIMessage({
|
|
188
|
+
content: part.text != null ? part.text : '',
|
|
189
|
+
});
|
|
241
190
|
formattedMessages.push(lastAIMessage);
|
|
242
191
|
}
|
|
243
192
|
else if (part.type === ContentTypes.TOOL_CALL) {
|
|
@@ -254,12 +203,9 @@ function formatAssistantMessage(message, options) {
|
|
|
254
203
|
}
|
|
255
204
|
if (!lastAIMessage) {
|
|
256
205
|
// "Heal" the payload by creating an AIMessage to precede the tool call
|
|
257
|
-
lastAIMessage =
|
|
206
|
+
lastAIMessage = new AIMessage({ content: '' });
|
|
258
207
|
formattedMessages.push(lastAIMessage);
|
|
259
208
|
}
|
|
260
|
-
else {
|
|
261
|
-
attachPendingReasoningContent(lastAIMessage);
|
|
262
|
-
}
|
|
263
209
|
const tool_call = _tool_call;
|
|
264
210
|
// TODO: investigate; args as dictionary may need to be providers-or-tool-specific
|
|
265
211
|
let args = _args;
|
|
@@ -295,11 +241,9 @@ function formatAssistantMessage(message, options) {
|
|
|
295
241
|
}
|
|
296
242
|
else if (part.type === ContentTypes.THINK ||
|
|
297
243
|
part.type === ContentTypes.THINKING ||
|
|
298
|
-
part.type === ContentTypes.REASONING ||
|
|
299
244
|
part.type === ContentTypes.REASONING_CONTENT ||
|
|
300
245
|
part.type === 'redacted_thinking') {
|
|
301
246
|
hasReasoning = true;
|
|
302
|
-
pendingReasoningContent += extractReasoningContent(part);
|
|
303
247
|
continue;
|
|
304
248
|
}
|
|
305
249
|
else if (part.type === ContentTypes.ERROR ||
|
|
@@ -325,11 +269,11 @@ function formatAssistantMessage(message, options) {
|
|
|
325
269
|
}, '')
|
|
326
270
|
.trim();
|
|
327
271
|
if (content) {
|
|
328
|
-
formattedMessages.push(
|
|
272
|
+
formattedMessages.push(new AIMessage({ content }));
|
|
329
273
|
}
|
|
330
274
|
}
|
|
331
275
|
else if (currentContent.length > 0) {
|
|
332
|
-
formattedMessages.push(
|
|
276
|
+
formattedMessages.push(new AIMessage({ content: currentContent }));
|
|
333
277
|
}
|
|
334
278
|
return formattedMessages;
|
|
335
279
|
}
|
|
@@ -564,10 +508,10 @@ function extractSkillName(args) {
|
|
|
564
508
|
* When SkillTool is invoked, the body is injected as a HumanMessage into LangGraph state
|
|
565
509
|
* but NOT persisted to conversation history. On follow-up runs the skill body is lost.
|
|
566
510
|
* Pass `skills` to reconstruct the HumanMessage at the right position after each skill
|
|
567
|
-
* ToolMessage. PRs #96 / #97.
|
|
511
|
+
* ToolMessage. Upstream PRs #96 / #97.
|
|
568
512
|
* @returns - Object containing formatted messages and updated indexTokenCountMap if provided.
|
|
569
513
|
*/
|
|
570
|
-
const formatAgentMessages = (payload, indexTokenCountMap, tools, skills
|
|
514
|
+
const formatAgentMessages = (payload, indexTokenCountMap, tools, skills) => {
|
|
571
515
|
const messages = [];
|
|
572
516
|
// If indexTokenCountMap is provided, create a new map to track the updated indices
|
|
573
517
|
const updatedIndexTokenCountMap = {};
|
|
@@ -645,7 +589,7 @@ const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options
|
|
|
645
589
|
if (discoveredTools.has(toolName)) {
|
|
646
590
|
/** Valid tool - keep it */
|
|
647
591
|
filteredContent.push(part);
|
|
648
|
-
// Skill body reconstruction (PRs #96/#97): when a
|
|
592
|
+
// Skill body reconstruction (upstream PRs #96/#97): when a
|
|
649
593
|
// SkillTool tool_call is present in this assistant turn, mark
|
|
650
594
|
// the skill name so we can reconstruct the body HumanMessage
|
|
651
595
|
// after the assistant turn has been pushed.
|
|
@@ -731,13 +675,11 @@ const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options
|
|
|
731
675
|
}
|
|
732
676
|
}
|
|
733
677
|
// Process the assistant message using the helper function
|
|
734
|
-
const formattedMessages = formatAssistantMessage(processedMessage
|
|
735
|
-
preserveReasoningContent: options?.provider === Providers.DEEPSEEK,
|
|
736
|
-
});
|
|
678
|
+
const formattedMessages = formatAssistantMessage(processedMessage);
|
|
737
679
|
messages.push(...formattedMessages);
|
|
738
680
|
// Capture index range BEFORE skill body injection so injected
|
|
739
681
|
// HumanMessages are excluded from the assistant's token distribution
|
|
740
|
-
// (PR #97 fix).
|
|
682
|
+
// (upstream PR #97 fix).
|
|
741
683
|
const endMessageIndex = messages.length;
|
|
742
684
|
if (pendingSkillNames?.size) {
|
|
743
685
|
for (const skillName of pendingSkillNames) {
|