illuma-agents 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE +1 -1
  2. package/dist/cjs/agents/AgentContext.cjs +236 -27
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/common/enum.cjs +2 -0
  5. package/dist/cjs/common/enum.cjs.map +1 -1
  6. package/dist/cjs/events.cjs +3 -11
  7. package/dist/cjs/events.cjs.map +1 -1
  8. package/dist/cjs/graphs/Graph.cjs +44 -18
  9. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  10. package/dist/cjs/instrumentation.cjs +1 -3
  11. package/dist/cjs/instrumentation.cjs.map +1 -1
  12. package/dist/cjs/llm/bedrock/index.cjs +121 -6
  13. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  14. package/dist/cjs/main.cjs +18 -0
  15. package/dist/cjs/main.cjs.map +1 -1
  16. package/dist/cjs/messages/cache.cjs +149 -54
  17. package/dist/cjs/messages/cache.cjs.map +1 -1
  18. package/dist/cjs/messages/tools.cjs +85 -0
  19. package/dist/cjs/messages/tools.cjs.map +1 -0
  20. package/dist/cjs/run.cjs +0 -8
  21. package/dist/cjs/run.cjs.map +1 -1
  22. package/dist/cjs/tools/CodeExecutor.cjs +4 -0
  23. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  24. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +438 -0
  25. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
  26. package/dist/cjs/tools/ToolNode.cjs +53 -15
  27. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  28. package/dist/cjs/tools/ToolSearchRegex.cjs +455 -0
  29. package/dist/cjs/tools/ToolSearchRegex.cjs.map +1 -0
  30. package/dist/cjs/tools/search/schema.cjs +7 -9
  31. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  32. package/dist/cjs/utils/run.cjs +5 -1
  33. package/dist/cjs/utils/run.cjs.map +1 -1
  34. package/dist/esm/agents/AgentContext.mjs +236 -27
  35. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  36. package/dist/esm/common/enum.mjs +2 -0
  37. package/dist/esm/common/enum.mjs.map +1 -1
  38. package/dist/esm/events.mjs +4 -12
  39. package/dist/esm/events.mjs.map +1 -1
  40. package/dist/esm/graphs/Graph.mjs +45 -19
  41. package/dist/esm/graphs/Graph.mjs.map +1 -1
  42. package/dist/esm/instrumentation.mjs +1 -3
  43. package/dist/esm/instrumentation.mjs.map +1 -1
  44. package/dist/esm/llm/bedrock/index.mjs +121 -6
  45. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  46. package/dist/esm/main.mjs +3 -0
  47. package/dist/esm/main.mjs.map +1 -1
  48. package/dist/esm/messages/cache.mjs +149 -54
  49. package/dist/esm/messages/cache.mjs.map +1 -1
  50. package/dist/esm/messages/tools.mjs +82 -0
  51. package/dist/esm/messages/tools.mjs.map +1 -0
  52. package/dist/esm/run.mjs +0 -8
  53. package/dist/esm/run.mjs.map +1 -1
  54. package/dist/esm/tools/CodeExecutor.mjs +4 -0
  55. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  56. package/dist/esm/tools/ProgrammaticToolCalling.mjs +430 -0
  57. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
  58. package/dist/esm/tools/ToolNode.mjs +53 -15
  59. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  60. package/dist/esm/tools/ToolSearchRegex.mjs +448 -0
  61. package/dist/esm/tools/ToolSearchRegex.mjs.map +1 -0
  62. package/dist/esm/tools/search/schema.mjs +7 -9
  63. package/dist/esm/tools/search/schema.mjs.map +1 -1
  64. package/dist/esm/utils/run.mjs +5 -1
  65. package/dist/esm/utils/run.mjs.map +1 -1
  66. package/dist/types/agents/AgentContext.d.ts +72 -5
  67. package/dist/types/common/enum.d.ts +2 -0
  68. package/dist/types/graphs/Graph.d.ts +3 -2
  69. package/dist/types/index.d.ts +2 -0
  70. package/dist/types/llm/bedrock/index.d.ts +31 -4
  71. package/dist/types/messages/cache.d.ts +23 -8
  72. package/dist/types/messages/index.d.ts +1 -0
  73. package/dist/types/messages/tools.d.ts +17 -0
  74. package/dist/types/test/mockTools.d.ts +28 -0
  75. package/dist/types/tools/ProgrammaticToolCalling.d.ts +91 -0
  76. package/dist/types/tools/ToolNode.d.ts +10 -2
  77. package/dist/types/tools/ToolSearchRegex.d.ts +80 -0
  78. package/dist/types/types/graph.d.ts +14 -1
  79. package/dist/types/types/tools.d.ts +138 -0
  80. package/package.json +7 -8
  81. package/src/agents/AgentContext.ts +278 -27
  82. package/src/agents/__tests__/AgentContext.test.ts +805 -0
  83. package/src/common/enum.ts +2 -0
  84. package/src/events.ts +5 -12
  85. package/src/graphs/Graph.ts +57 -19
  86. package/src/index.ts +2 -0
  87. package/src/instrumentation.ts +1 -4
  88. package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +473 -0
  89. package/src/llm/bedrock/index.ts +149 -12
  90. package/src/messages/__tests__/tools.test.ts +473 -0
  91. package/src/messages/cache.ts +163 -61
  92. package/src/messages/index.ts +1 -0
  93. package/src/messages/tools.ts +99 -0
  94. package/src/run.ts +0 -9
  95. package/src/scripts/code_exec_ptc.ts +334 -0
  96. package/src/scripts/image.ts +178 -0
  97. package/src/scripts/programmatic_exec.ts +396 -0
  98. package/src/scripts/programmatic_exec_agent.ts +231 -0
  99. package/src/scripts/test-tools-before-handoff.ts +5 -1
  100. package/src/scripts/tool_search_regex.ts +162 -0
  101. package/src/scripts/tools.ts +4 -1
  102. package/src/specs/thinking-prune.test.ts +52 -118
  103. package/src/test/mockTools.ts +366 -0
  104. package/src/tools/CodeExecutor.ts +4 -0
  105. package/src/tools/ProgrammaticToolCalling.ts +558 -0
  106. package/src/tools/ToolNode.ts +59 -18
  107. package/src/tools/ToolSearchRegex.ts +535 -0
  108. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +318 -0
  109. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +853 -0
  110. package/src/tools/__tests__/ToolSearchRegex.integration.test.ts +161 -0
  111. package/src/tools/__tests__/ToolSearchRegex.test.ts +232 -0
  112. package/src/tools/search/jina-reranker.test.ts +16 -16
  113. package/src/tools/search/schema.ts +7 -9
  114. package/src/types/graph.ts +14 -1
  115. package/src/types/tools.ts +166 -0
  116. package/src/utils/run.ts +5 -1
  117. package/src/tools/search/direct-url.test.ts +0 -530
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes contentBlockIndex conflicts\n *\n * Bedrock sends the same contentBlockIndex for both text and tool_use content blocks,\n * causing LangChain's merge logic to fail with \"field[contentBlockIndex] already exists\"\n * errors. This wrapper simply strips contentBlockIndex from response_metadata to avoid\n * the conflict.\n *\n * The contentBlockIndex field is only used internally by Bedrock's streaming protocol\n * and isn't needed by application logic - the index field on tool_call_chunks serves\n * the purpose of tracking tool call ordering.\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\n\nexport class CustomChatBedrockConverse extends ChatBedrockConverse {\n constructor(fields?: ChatBedrockConverseInput) {\n super(fields);\n }\n\n static lc_name(): string {\n return 'IllumaBedrockConverse';\n }\n\n /**\n * Override _streamResponseChunks to strip contentBlockIndex from response_metadata\n * This prevents LangChain's merge conflicts when the same index is used for\n * different content types (text vs tool calls)\n */\n async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'],\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const baseStream = super._streamResponseChunks(\n messages,\n options,\n runManager\n );\n\n for await (const chunk of baseStream) {\n // Only process if we have response_metadata\n if (\n chunk.message instanceof AIMessageChunk &&\n (chunk.message as Partial<AIMessageChunk>).response_metadata &&\n typeof chunk.message.response_metadata === 'object'\n ) {\n // Check if contentBlockIndex exists anywhere in response_metadata (top level or nested)\n const hasContentBlockIndex = this.hasContentBlockIndex(\n chunk.message.response_metadata\n );\n\n if (hasContentBlockIndex) {\n const cleanedMetadata = this.removeContentBlockIndex(\n chunk.message.response_metadata\n ) as Record<string, unknown>;\n\n yield new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...chunk.message,\n response_metadata: cleanedMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n continue;\n }\n }\n\n yield chunk;\n }\n }\n\n /**\n * Check if contentBlockIndex exists at any level in the object\n */\n private hasContentBlockIndex(obj: unknown): boolean {\n if (obj === null || obj === undefined || typeof obj !== 'object') {\n return false;\n }\n\n if ('contentBlockIndex' in obj) {\n return true;\n }\n\n for (const value of Object.values(obj)) {\n if (typeof value === 'object' && value !== null) {\n if (this.hasContentBlockIndex(value)) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n /**\n * Recursively remove contentBlockIndex from all levels of an object\n */\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\nexport type { ChatBedrockConverseInput };\n"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;AAWG;AASG,MAAO,yBAA0B,SAAQ,mBAAmB,CAAA;AAChE,IAAA,WAAA,CAAY,MAAiC,EAAA;QAC3C,KAAK,CAAC,MAAM,CAAC;;AAGf,IAAA,OAAO,OAAO,GAAA;AACZ,QAAA,OAAO,uBAAuB;;AAGhC;;;;AAIG;IACH,OAAO,qBAAqB,CAC1B,QAAuB,EACvB,OAAkC,EAClC,UAAqC,EAAA;AAErC,QAAA,MAAM,UAAU,GAAG,KAAK,CAAC,qBAAqB,CAC5C,QAAQ,EACR,OAAO,EACP,UAAU,CACX;AAED,QAAA,WAAW,MAAM,KAAK,IAAI,UAAU,EAAE;;AAEpC,YAAA,IACE,KAAK,CAAC,OAAO,YAAY,cAAc;gBACtC,KAAK,CAAC,OAAmC,CAAC,iBAAiB;gBAC5D,OAAO,KAAK,CAAC,OAAO,CAAC,iBAAiB,KAAK,QAAQ,EACnD;;AAEA,gBAAA,MAAM,oBAAoB,GAAG,IAAI,CAAC,oBAAoB,CACpD,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAChC;gBAED,IAAI,oBAAoB,EAAE;AACxB,oBAAA,MAAM,eAAe,GAAG,IAAI,CAAC,uBAAuB,CAClD,KAAK,CAAC,OAAO,CAAC,iBAAiB,CACL;oBAE5B,MAAM,IAAI,mBAAmB,CAAC;wBAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,OAAO,EAAE,IAAI,cAAc,CAAC;4BAC1B,GAAG,KAAK,CAAC,OAAO;AAChB,4BAAA,iBAAiB,EAAE,eAAe;yBACnC,CAAC;wBACF,cAAc,EAAE,KAAK,CAAC,cAAc;AACrC,qBAAA,CAAC;oBACF;;;AAIJ,YAAA,MAAM,KAAK;;;AAIf;;AAEG;AACK,IAAA,oBAAoB,CAAC,GAAY,EAAA;AACvC,QAAA,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AAChE,YAAA,OAAO,KAAK;;AAGd,QAAA,IAAI,mBAAmB,IAAI,GAAG,EAAE;AAC9B,YAAA,OAAO,IAAI;;QAGb,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YACtC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE;AAC/C,gBAAA,IAAI,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE;AACpC,oBAAA,OAAO,IAAI;;;;AAKjB,QAAA,OAAO,KAAK;;AAGd;;AAEG;AACK,IAAA,uBAAuB,CAAC,GAAY,EAAA;QAC1C,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE;AACrC,YAAA,OAAO,GAAG;;AAGZ,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;AACtB,YAAA,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC;;AAG9D,QAAA,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;YAC3B,MAAM,OAAO,GAA4B,EAAE;AAC3C,YAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;AAC9C,gBAAA,IAAI,GAAG,KAAK,mBAAmB,EAAE;oBAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC;;;AAGtD,YAAA,OAAO,OAAO;;AAGhB,QAAA,OAAO,GAAG;;AAEb;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes contentBlockIndex conflicts\n * and adds prompt caching support for Bedrock Converse API.\n *\n * Bedrock sends the same contentBlockIndex for both text and tool_use content blocks,\n * causing LangChain's merge logic to fail with \"field[contentBlockIndex] already exists\"\n * errors. This wrapper simply strips contentBlockIndex from response_metadata to avoid\n * the conflict.\n *\n * The contentBlockIndex field is only used internally by Bedrock's streaming protocol\n * and isn't needed by application logic - the index field on tool_call_chunks serves\n * the purpose of tracking tool call ordering.\n *\n * PROMPT CACHING:\n * When promptCache: true is set, this wrapper adds cachePoint markers to the tools array\n * to enable Bedrock prompt caching for tool definitions. This allows tool schemas to be\n * cached and reused across requests, reducing latency and costs.\n *\n * CACHE TOKEN EXTRACTION:\n * LangChain AWS doesn't extract cacheReadInputTokens/cacheWriteInputTokens from Bedrock's\n * response. This wrapper adds input_token_details to usage_metadata with cache information.\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { BaseMessage, UsageMetadata } from '@langchain/core/messages';\nimport { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\n\n/** Extended input type with promptCache option */\nexport interface CustomChatBedrockConverseInput extends ChatBedrockConverseInput {\n promptCache?: boolean;\n}\n\nexport class CustomChatBedrockConverse extends ChatBedrockConverse {\n promptCache: boolean;\n\n constructor(fields?: CustomChatBedrockConverseInput) {\n super(fields);\n this.promptCache = fields?.promptCache ?? false;\n }\n\n static lc_name(): string {\n return 'IllumaBedrockConverse';\n }\n\n /**\n * Override invocationParams to add cachePoint to tools when promptCache is enabled.\n * This enables Bedrock prompt caching for tool definitions.\n * \n * STRATEGY: Separate cachePoints for core tools and MCP tools\n * - Core tools (web_search, execute_code, etc.) are stable → cache first\n * - MCP tools (have '_mcp_' in name) are dynamic → cache separately after\n * - This allows core tools to stay cached when MCP selection changes\n * \n * NOTE: Only Claude models support cachePoint - Nova and other models will reject it.\n */\n invocationParams(\n options?: this['ParsedCallOptions']\n ): ReturnType<ChatBedrockConverse['invocationParams']> {\n const params = super.invocationParams(options);\n\n // Add cachePoint to tools array if promptCache is enabled and tools exist\n // Only Claude models support cachePoint - check model name\n const modelId = this.model?.toLowerCase() ?? '';\n const isClaudeModel = modelId.includes('claude') || modelId.includes('anthropic');\n\n if (\n this.promptCache &&\n isClaudeModel &&\n params.toolConfig?.tools &&\n Array.isArray(params.toolConfig.tools) &&\n params.toolConfig.tools.length > 0\n ) {\n // Separate core tools from MCP tools\n // MCP tools have '_mcp_' in their name (e.g., 'search_emails_mcp_Google-Workspace')\n const coreTools: typeof params.toolConfig.tools = [];\n const mcpTools: typeof params.toolConfig.tools = [];\n const coreToolNames: string[] = [];\n const mcpToolNames: string[] = [];\n \n for (const tool of params.toolConfig.tools) {\n // Check if tool has a name property with '_mcp_' pattern\n const toolName = (tool as { toolSpec?: { name?: string } })?.toolSpec?.name ?? '';\n if (toolName.includes('_mcp_')) {\n mcpTools.push(tool);\n mcpToolNames.push(toolName);\n } else {\n coreTools.push(tool);\n coreToolNames.push(toolName);\n }\n }\n\n // Always log cache structure (INFO level for tracking)\n console.log(`[Cache] 🔧 Tools | Core: [${coreToolNames.join(', ')}] (${coreTools.length}) | MCP: [${mcpToolNames.join(', ')}] (${mcpTools.length})`);\n\n // Build tools array with strategic cachePoints:\n // [CoreTool1, CoreTool2, cachePoint] + [MCPTool1, MCPTool2, cachePoint]\n const toolsWithCache: typeof params.toolConfig.tools = [];\n let cachePointCount = 0;\n \n // Add core tools with cachePoint (if any)\n if (coreTools.length > 0) {\n toolsWithCache.push(...coreTools);\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n cachePointCount++;\n }\n \n // Add MCP tools with their own cachePoint (if any)\n if (mcpTools.length > 0) {\n toolsWithCache.push(...mcpTools);\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n cachePointCount++;\n }\n \n // If no tools at all (shouldn't happen but safety check)\n if (toolsWithCache.length === 0) {\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n cachePointCount++;\n }\n \n console.log(`[Cache] 📍 Tool cachePoints: ${cachePointCount} | Order: [${coreToolNames.length > 0 ? 'CoreTools→CP' : ''}${mcpToolNames.length > 0 ? '→MCPTools→CP' : ''}]`);\n \n params.toolConfig.tools = toolsWithCache;\n }\n\n return params;\n }\n\n /**\n * Override _streamResponseChunks to:\n * 1. Strip contentBlockIndex from response_metadata to prevent merge conflicts\n * 2. Extract cacheReadInputTokens/cacheWriteInputTokens and add to usage_metadata\n */\n async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'],\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const baseStream = super._streamResponseChunks(\n messages,\n options,\n runManager\n );\n\n for await (const chunk of baseStream) {\n // Only process if we have response_metadata\n if (\n chunk.message instanceof AIMessageChunk &&\n (chunk.message as Partial<AIMessageChunk>).response_metadata &&\n typeof chunk.message.response_metadata === 'object'\n ) {\n const responseMetadata = chunk.message.response_metadata as Record<string, unknown>;\n let needsModification = false;\n let cleanedMetadata = responseMetadata;\n\n // Check if contentBlockIndex exists anywhere in response_metadata\n const hasContentBlockIndex = this.hasContentBlockIndex(responseMetadata);\n if (hasContentBlockIndex) {\n cleanedMetadata = this.removeContentBlockIndex(responseMetadata) as Record<string, unknown>;\n needsModification = true;\n }\n\n // Extract cache tokens from metadata.usage (Bedrock streaming format)\n // The metadata chunk contains usage with cacheReadInputTokens/cacheWriteInputTokens\n const metadata = responseMetadata.metadata as Record<string, unknown> | undefined;\n const usage = (metadata?.usage ?? responseMetadata.usage) as Record<string, unknown> | undefined;\n \n let enhancedUsageMetadata: UsageMetadata | undefined = chunk.message.usage_metadata;\n \n if (usage) {\n const cacheRead = (usage.cacheReadInputTokens as number) ?? 0;\n const cacheWrite = (usage.cacheWriteInputTokens as number) ?? 0;\n const inputTokens = (usage.inputTokens as number) ?? 0;\n const outputTokens = (usage.outputTokens as number) ?? 0;\n \n if (cacheRead > 0 || cacheWrite > 0) {\n // Always log cache results for tracking\n const cacheStatus = cacheRead > 0 && cacheWrite === 0 ? '✅ HIT' : \n cacheWrite > 0 && cacheRead === 0 ? '📝 WRITE' :\n cacheRead > 0 && cacheWrite > 0 ? '🔄 PARTIAL' : '❌ MISS';\n console.log(`[Cache] ${cacheStatus} | read=${cacheRead} | write=${cacheWrite} | input=${inputTokens} | output=${outputTokens}`);\n \n needsModification = true;\n enhancedUsageMetadata = {\n input_tokens: chunk.message.usage_metadata?.input_tokens ?? inputTokens,\n output_tokens: chunk.message.usage_metadata?.output_tokens ?? outputTokens,\n total_tokens: chunk.message.usage_metadata?.total_tokens ?? (usage.totalTokens as number) ?? 0,\n input_token_details: {\n cache_read: cacheRead,\n cache_creation: cacheWrite,\n },\n };\n }\n }\n\n if (needsModification) {\n yield new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...chunk.message,\n response_metadata: cleanedMetadata,\n usage_metadata: enhancedUsageMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n continue;\n }\n }\n\n yield chunk;\n }\n }\n\n /**\n * Check if contentBlockIndex exists at any level in the object\n */\n private hasContentBlockIndex(obj: unknown): boolean {\n if (obj === null || obj === undefined || typeof obj !== 'object') {\n return false;\n }\n\n if ('contentBlockIndex' in obj) {\n return true;\n }\n\n for (const value of Object.values(obj)) {\n if (typeof value === 'object' && value !== null) {\n if (this.hasContentBlockIndex(value)) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n /**\n * Recursively remove contentBlockIndex from all levels of an object\n */\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\nexport type { ChatBedrockConverseInput };\n"],"names":[],"mappings":"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"}
package/dist/esm/main.mjs CHANGED
@@ -8,10 +8,13 @@ export { calculateTotalTokens, checkValidNumber, createPruneMessages, getMessage
8
8
  export { ensureThinkingBlockInMessages, formatAgentMessages, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, labelContentByAgent, shiftIndexTokenCountMap } from './messages/format.mjs';
9
9
  export { addBedrockCacheControl, addCacheControl, stripAnthropicCacheControl, stripBedrockCacheControl } from './messages/cache.mjs';
10
10
  export { formatContentStrings } from './messages/content.mjs';
11
+ export { extractToolDiscoveries, hasToolSearchInCurrentTurn } from './messages/tools.mjs';
11
12
  export { Graph, StandardGraph } from './graphs/Graph.mjs';
12
13
  export { MultiAgentGraph } from './graphs/MultiAgentGraph.mjs';
13
14
  export { Calculator } from './tools/Calculator.mjs';
14
15
  export { createCodeExecutionTool, getCodeBaseURL, imageExtRegex } from './tools/CodeExecutor.mjs';
16
+ export { createProgrammaticToolCallingTool, executeTools, extractUsedToolNames, filterToolsByUsage, formatCompletedResponse, makeRequest, normalizeToPythonIdentifier } from './tools/ProgrammaticToolCalling.mjs';
17
+ export { countNestedGroups, createToolSearchRegexTool, escapeRegexSpecialChars, hasNestedQuantifiers, isDangerousPattern, sanitizeRegex } from './tools/ToolSearchRegex.mjs';
15
18
  export { handleServerToolResult, handleToolCallChunks, handleToolCalls, toolResultTypes } from './tools/handlers.mjs';
16
19
  export { createSearchTool } from './tools/search/tool.mjs';
17
20
  export { Callback, CommonEvents, Constants, ContentTypes, EnvVar, GraphEvents, GraphNodeActions, GraphNodeKeys, Providers, StepTypes, TitleMethod, ToolCallTypes } from './common/enum.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"main.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"main.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,15 @@
1
1
  import { ContentTypes } from '../common/enum.mjs';
2
2
 
3
+ /** Always-on logger for cache operations */
4
+ const logCache = (message) => {
5
+ console.log(`[Cache] ${message}`);
6
+ };
7
+ /** Debug logger for cache operations - set ILLUMA_DEBUG_CACHE=true to enable */
8
+ const debugCache = (message, data) => {
9
+ if (process.env.ILLUMA_DEBUG_CACHE === 'true') {
10
+ console.log(`[Cache] ${message}`, data !== undefined ? JSON.stringify(data, null, 2) : '');
11
+ }
12
+ };
3
13
  /**
4
14
  * Anthropic API: Adds cache control to the appropriate user messages in the payload.
5
15
  * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
@@ -103,29 +113,46 @@ function stripBedrockCacheControl(messages) {
103
113
  return updatedMessages;
104
114
  }
105
115
  /**
106
- * Adds Bedrock Converse API cache points to the last two messages.
107
- * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block
108
- * immediately after the last text block in each targeted message.
109
- * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
110
- * then adds fresh cache points to the last 2 messages in a single backward pass.
111
- * This ensures we don't accumulate stale cache points across multiple turns.
112
- * @param messages - The array of message objects.
113
- * @returns - The updated array of message objects with cache points added.
116
+ * Adds Bedrock Converse API cache points using "Stable Prefix Caching" strategy.
117
+ *
118
+ * STRATEGY: Place cache point after the LAST ASSISTANT message only.
119
+ * This ensures the prefix (everything before the cache point) remains STABLE
120
+ * as the conversation grows, maximizing cache hits.
121
+ *
122
+ * Why this works:
123
+ * - System message has its own cachePoint (added in AgentContext)
124
+ * - Tools have their own cachePoint (added in CustomChatBedrockConverse)
125
+ * - Conversation history grows, but the PREFIX stays the same
126
+ * - Only the NEW user message is uncached (it's always different)
127
+ *
128
+ * Example conversation flow:
129
+ * Request 1: [System+cachePoint][Tools+cachePoint][User1] → No conversation cache yet
130
+ * Request 2: [System][Tools][User1][Assistant1+cachePoint][User2] → Cache User1+Assistant1
131
+ * Request 3: [System][Tools][User1][Assistant1][User2][Assistant2+cachePoint][User3]
132
+ * → Cache reads User1+A1+User2+A2, cache writes new portion
133
+ *
134
+ * Claude's "Simplified Cache Management" automatically looks back up to 20 content
135
+ * blocks from the cache checkpoint to find the longest matching prefix.
136
+ *
137
+ * @param messages - The array of message objects (excluding system message).
138
+ * @returns - The updated array with a single cache point after the last assistant message.
114
139
  */
115
140
  function addBedrockCacheControl(messages) {
116
- if (!Array.isArray(messages) || messages.length < 2) {
141
+ if (!Array.isArray(messages) || messages.length < 1) {
142
+ debugCache('addBedrockCacheControl: Skipping - no messages', { count: messages?.length });
117
143
  return messages;
118
144
  }
145
+ debugCache('addBedrockCacheControl: Processing messages with stable prefix strategy', {
146
+ count: messages.length
147
+ });
119
148
  const updatedMessages = messages.slice();
120
- let messagesModified = 0;
121
- for (let i = updatedMessages.length - 1; i >= 0; i--) {
122
- const message = updatedMessages[i];
123
- const isToolMessage = 'getType' in message &&
124
- typeof message.getType === 'function' &&
125
- message.getType() === 'tool';
149
+ // First pass: Remove ALL existing cache points to ensure clean state
150
+ // This prevents accumulation of stale cache points
151
+ for (const message of updatedMessages) {
126
152
  const content = message.content;
127
153
  if (Array.isArray(content)) {
128
154
  message.content = content.filter((block) => !isCachePoint(block));
155
+ // Also remove Anthropic-style cache_control
129
156
  for (let j = 0; j < message.content.length; j++) {
130
157
  const block = message.content[j];
131
158
  if ('cache_control' in block) {
@@ -133,57 +160,125 @@ function addBedrockCacheControl(messages) {
133
160
  }
134
161
  }
135
162
  }
136
- if (messagesModified >= 2 || isToolMessage) {
137
- continue;
138
- }
139
- if (typeof content === 'string' && content === '') {
140
- continue;
141
- }
142
- if (typeof content === 'string') {
143
- message.content = [
144
- { type: ContentTypes.TEXT, text: content },
145
- { cachePoint: { type: 'default' } },
146
- ];
147
- messagesModified++;
148
- continue;
163
+ }
164
+ // Helper function to check if a message contains reasoning/thinking blocks
165
+ const hasReasoningBlock = (message) => {
166
+ const content = message.content;
167
+ if (!Array.isArray(content)) {
168
+ return false;
149
169
  }
150
- if (Array.isArray(message.content)) {
151
- let hasCacheableContent = false;
152
- for (const block of message.content) {
153
- if (block.type === ContentTypes.TEXT) {
154
- if (typeof block.text === 'string' && block.text !== '') {
155
- hasCacheableContent = true;
156
- break;
157
- }
158
- }
170
+ for (const block of content) {
171
+ const type = block.type;
172
+ // Check for all reasoning/thinking block types:
173
+ // - reasoning_content: Bedrock Anthropic extended thinking
174
+ // - reasoning: Generic reasoning format
175
+ // - thinking: Anthropic direct API thinking
176
+ // - redacted_thinking: Anthropic redacted thinking blocks
177
+ if (type === 'reasoning_content' ||
178
+ type === 'reasoning' ||
179
+ type === 'thinking' ||
180
+ type === 'redacted_thinking') {
181
+ return true;
159
182
  }
160
- if (!hasCacheableContent) {
183
+ }
184
+ return false;
185
+ };
186
+ // Second pass: Find the LAST assistant message WITHOUT reasoning blocks and add a cache point there
187
+ // Messages with reasoning/thinking blocks cannot have cache points after them (Bedrock limitation)
188
+ let lastAssistantIndex = -1;
189
+ let skippedWithReasoning = 0;
190
+ // Count message types for logging
191
+ const messageTypes = {};
192
+ for (const message of updatedMessages) {
193
+ const msgType = 'getType' in message && typeof message.getType === 'function'
194
+ ? message.getType()
195
+ : 'unknown';
196
+ messageTypes[msgType] = (messageTypes[msgType] || 0) + 1;
197
+ }
198
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
199
+ const message = updatedMessages[i];
200
+ const messageType = 'getType' in message && typeof message.getType === 'function'
201
+ ? message.getType()
202
+ : 'unknown';
203
+ if (messageType === 'ai') {
204
+ // Skip assistant messages with reasoning blocks - cache points not allowed after them
205
+ if (hasReasoningBlock(message)) {
206
+ skippedWithReasoning++;
207
+ debugCache('addBedrockCacheControl: Skipping assistant message with reasoning block', { index: i });
161
208
  continue;
162
209
  }
163
- let inserted = false;
164
- for (let j = message.content.length - 1; j >= 0; j--) {
165
- const block = message.content[j];
166
- const type = block.type;
167
- if (type === ContentTypes.TEXT || type === 'text') {
168
- const text = block.text;
169
- if (text === '' || text === undefined) {
170
- continue;
171
- }
172
- message.content.splice(j + 1, 0, {
210
+ lastAssistantIndex = i;
211
+ break;
212
+ }
213
+ }
214
+ // Log message summary
215
+ logCache(`📨 Messages | total=${updatedMessages.length} | ${Object.entries(messageTypes).map(([k, v]) => `${k}:${v}`).join(' ')} | skippedReasoning=${skippedWithReasoning}`);
216
+ // If no suitable assistant message found, skip conversation caching
217
+ // (System and Tools caching are still handled separately)
218
+ if (lastAssistantIndex === -1) {
219
+ logCache('📨 Messages | No suitable assistant message for cachePoint (first turn or all have reasoning)');
220
+ return updatedMessages;
221
+ }
222
+ // Add cache point to the last assistant message (without reasoning blocks)
223
+ const assistantMessage = updatedMessages[lastAssistantIndex];
224
+ const content = assistantMessage.content;
225
+ if (typeof content === 'string' && content !== '') {
226
+ assistantMessage.content = [
227
+ { type: ContentTypes.TEXT, text: content },
228
+ { cachePoint: { type: 'default' } },
229
+ ];
230
+ logCache(`📍 Message cachePoint at index ${lastAssistantIndex} (string, ${content.length} chars)`);
231
+ debugCache('addBedrockCacheControl: Added cachePoint to assistant message (string content)', {
232
+ index: lastAssistantIndex,
233
+ contentLength: content.length,
234
+ });
235
+ }
236
+ else if (Array.isArray(assistantMessage.content) && assistantMessage.content.length > 0) {
237
+ // Double-check: If this message has reasoning blocks, skip adding cache point entirely
238
+ // This handles edge cases where the initial skip check might have missed it
239
+ if (hasReasoningBlock(assistantMessage)) {
240
+ logCache(`⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (has reasoning blocks)`);
241
+ debugCache('addBedrockCacheControl: Skipping - assistant message has reasoning blocks (safety check)', {
242
+ index: lastAssistantIndex,
243
+ });
244
+ return updatedMessages;
245
+ }
246
+ // Find the last text block and insert cache point after it
247
+ let inserted = false;
248
+ for (let j = assistantMessage.content.length - 1; j >= 0; j--) {
249
+ const block = assistantMessage.content[j];
250
+ const type = block.type;
251
+ if (type === ContentTypes.TEXT || type === 'text') {
252
+ const text = block.text;
253
+ if (text && text !== '') {
254
+ assistantMessage.content.splice(j + 1, 0, {
173
255
  cachePoint: { type: 'default' },
174
256
  });
175
257
  inserted = true;
258
+ logCache(`📍 Message cachePoint at index ${lastAssistantIndex} (array, block ${j}, ${text.length} chars)`);
259
+ debugCache('addBedrockCacheControl: Added cachePoint after text block in assistant message', {
260
+ index: lastAssistantIndex,
261
+ textBlockIndex: j,
262
+ contentLength: text.length,
263
+ });
176
264
  break;
177
265
  }
178
266
  }
179
- if (!inserted) {
180
- message.content.push({
181
- cachePoint: { type: 'default' },
182
- });
183
- }
184
- messagesModified++;
267
+ }
268
+ // If no text block found, don't append cache point as the message structure is unexpected
269
+ if (!inserted) {
270
+ const contentTypes = assistantMessage.content.map((b) => b.type);
271
+ logCache(`⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (no text block, types: ${contentTypes.join(',')})`);
272
+ debugCache('addBedrockCacheControl: No suitable text block found, skipping cache point', {
273
+ index: lastAssistantIndex,
274
+ contentTypes,
275
+ });
185
276
  }
186
277
  }
278
+ debugCache('addBedrockCacheControl: Complete - stable prefix caching applied', {
279
+ lastAssistantIndex,
280
+ totalMessages: updatedMessages.length,
281
+ });
187
282
  return updatedMessages;
188
283
  }
189
284
 
@@ -1 +1 @@
1
- {"version":3,"file":"cache.mjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import { BaseMessage, MessageContentComplex } 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/**\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 * @param messages - The array of message objects.\n * @returns - The updated 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 = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const isUserMessage =\n ('getType' in message && message.getType() === 'human') ||\n ('role' in message && message.role === 'user');\n\n if (Array.isArray(message.content)) {\n message.content = message.content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof message.content;\n\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (userMessagesModified >= 2 || !isUserMessage) {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n {\n type: 'text',\n text: message.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n userMessagesModified++;\n } else if (Array.isArray(message.content)) {\n for (let j = message.content.length - 1; j >= 0; j--) {\n const contentPart = message.content[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 }\n\n return updatedMessages;\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 * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\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 = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n for (let j = 0; j < content.length; j++) {\n const block = content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\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 */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof content;\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the last two messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,\n * then adds fresh cache points to the last 2 messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends Partial<BaseMessage> & MessageWithContent,\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = messages.slice();\n let messagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const isToolMessage =\n 'getType' in message &&\n typeof message.getType === 'function' &&\n message.getType() === 'tool';\n\n const content = message.content;\n\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block)\n ) as typeof content;\n\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (messagesModified >= 2 || isToolMessage) {\n continue;\n }\n\n if (typeof content === 'string' && content === '') {\n continue;\n }\n\n if (typeof content === 'string') {\n message.content = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n messagesModified++;\n continue;\n }\n\n if (Array.isArray(message.content)) {\n let hasCacheableContent = false;\n for (const block of message.content) {\n if (block.type === ContentTypes.TEXT) {\n if (typeof block.text === 'string' && block.text !== '') {\n hasCacheableContent = true;\n break;\n }\n }\n }\n\n if (!hasCacheableContent) {\n continue;\n }\n\n let inserted = false;\n for (let j = message.content.length - 1; j >= 0; j--) {\n const block = message.content[j] as MessageContentComplex;\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n if (text === '' || text === undefined) {\n continue;\n }\n message.content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n message.content.push({\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n }\n messagesModified++;\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 { BaseMessage, MessageContentComplex } 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/** Always-on logger for cache operations */\nconst logCache = (message: string) => {\n console.log(`[Cache] ${message}`);\n};\n\n/** Debug logger for cache operations - set ILLUMA_DEBUG_CACHE=true to enable */\nconst debugCache = (message: string, data?: unknown) => {\n if (process.env.ILLUMA_DEBUG_CACHE === 'true') {\n console.log(`[Cache] ${message}`, data !== undefined ? JSON.stringify(data, null, 2) : '');\n }\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 * @param messages - The array of message objects.\n * @returns - The updated 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 = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const isUserMessage =\n ('getType' in message && message.getType() === 'human') ||\n ('role' in message && message.role === 'user');\n\n if (Array.isArray(message.content)) {\n message.content = message.content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof message.content;\n\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (userMessagesModified >= 2 || !isUserMessage) {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n {\n type: 'text',\n text: message.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n userMessagesModified++;\n } else if (Array.isArray(message.content)) {\n for (let j = message.content.length - 1; j >= 0; j--) {\n const contentPart = message.content[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 }\n\n return updatedMessages;\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 * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\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 = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n for (let j = 0; j < content.length; j++) {\n const block = content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\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 */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof content;\n }\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 CustomChatBedrockConverse)\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', { count: messages?.length });\n return messages;\n }\n\n debugCache('addBedrockCacheControl: Processing messages with stable prefix strategy', { \n count: messages.length \n });\n \n const updatedMessages: T[] = messages.slice();\n \n // First pass: Remove ALL existing cache points to ensure clean state\n // This prevents accumulation of stale cache points\n for (const message of updatedMessages) {\n const content = message.content;\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block)\n ) as typeof content;\n\n // Also remove Anthropic-style cache_control\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\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 // Check for all reasoning/thinking block types:\n // - reasoning_content: Bedrock Anthropic extended thinking\n // - reasoning: Generic reasoning format \n // - thinking: Anthropic direct API thinking\n // - redacted_thinking: Anthropic redacted thinking blocks\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 // Second pass: Find the LAST assistant message WITHOUT reasoning blocks and add a cache point there\n // Messages with reasoning/thinking blocks cannot have cache points after them (Bedrock limitation)\n let lastAssistantIndex = -1;\n let skippedWithReasoning = 0;\n \n // Count message types for logging\n const messageTypes: Record<string, number> = {};\n for (const message of updatedMessages) {\n const msgType = 'getType' in message && typeof message.getType === 'function' \n ? message.getType() \n : 'unknown';\n messageTypes[msgType] = (messageTypes[msgType] || 0) + 1;\n }\n \n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const messageType = 'getType' in message && typeof message.getType === 'function' \n ? message.getType() \n : 'unknown';\n \n if (messageType === 'ai') {\n // Skip assistant messages with reasoning blocks - cache points not allowed after them\n if (hasReasoningBlock(message)) {\n skippedWithReasoning++;\n debugCache('addBedrockCacheControl: Skipping assistant message with reasoning block', { index: i });\n continue;\n }\n lastAssistantIndex = i;\n break;\n }\n }\n\n // Log message summary\n logCache(`📨 Messages | total=${updatedMessages.length} | ${Object.entries(messageTypes).map(([k,v]) => `${k}:${v}`).join(' ')} | skippedReasoning=${skippedWithReasoning}`);\n\n // If no suitable assistant message found, skip conversation caching\n // (System and Tools caching are still handled separately)\n if (lastAssistantIndex === -1) {\n logCache('📨 Messages | No suitable assistant message for cachePoint (first turn or all have reasoning)');\n return updatedMessages;\n }\n\n // Add cache point to the last assistant message (without reasoning blocks)\n const assistantMessage = updatedMessages[lastAssistantIndex];\n const content = assistantMessage.content;\n\n if (typeof content === 'string' && content !== '') {\n assistantMessage.content = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n logCache(`📍 Message cachePoint at index ${lastAssistantIndex} (string, ${content.length} chars)`);\n debugCache('addBedrockCacheControl: Added cachePoint to assistant message (string content)', {\n index: lastAssistantIndex,\n contentLength: content.length,\n });\n } else if (Array.isArray(assistantMessage.content) && assistantMessage.content.length > 0) {\n // Double-check: If this message has reasoning blocks, skip adding cache point entirely\n // This handles edge cases where the initial skip check might have missed it\n if (hasReasoningBlock(assistantMessage)) {\n logCache(`⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (has reasoning blocks)`);\n debugCache('addBedrockCacheControl: Skipping - assistant message has reasoning blocks (safety check)', {\n index: lastAssistantIndex,\n });\n return updatedMessages;\n }\n \n // Find the last text block and insert cache point after it\n let inserted = false;\n for (let j = assistantMessage.content.length - 1; j >= 0; j--) {\n const block = assistantMessage.content[j] as MessageContentComplex;\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n if (text && text !== '') {\n assistantMessage.content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n logCache(`📍 Message cachePoint at index ${lastAssistantIndex} (array, block ${j}, ${text.length} chars)`);\n debugCache('addBedrockCacheControl: Added cachePoint after text block in assistant message', {\n index: lastAssistantIndex,\n textBlockIndex: j,\n contentLength: text.length,\n });\n break;\n }\n }\n }\n \n // If no text block found, don't append cache point as the message structure is unexpected\n if (!inserted) {\n const contentTypes = assistantMessage.content.map((b) => (b as { type?: string }).type);\n logCache(`⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (no text block, types: ${contentTypes.join(',')})`);\n debugCache('addBedrockCacheControl: No suitable text block found, skipping cache point', {\n index: lastAssistantIndex,\n contentTypes,\n });\n }\n }\n\n debugCache('addBedrockCacheControl: Complete - stable prefix caching applied', { \n lastAssistantIndex,\n totalMessages: updatedMessages.length,\n });\n\n return updatedMessages;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { ToolMessage, AIMessageChunk } from '@langchain/core/messages';
2
+ import { Constants } from '../common/enum.mjs';
3
+ import { findLastIndex } from './core.mjs';
4
+
5
+ // src/messages/toolDiscovery.ts
6
+ /**
7
+ * Extracts discovered tool names from tool search results in the current turn.
8
+ * Only processes tool search messages after the latest AI message with tool calls.
9
+ *
10
+ * Similar pattern to formatArtifactPayload - finds relevant messages efficiently
11
+ * by identifying the latest AI parent and only processing subsequent tool messages.
12
+ *
13
+ * @param messages - All messages in the conversation
14
+ * @returns Array of discovered tool names (empty if no new discoveries)
15
+ */
16
+ function extractToolDiscoveries(messages) {
17
+ const lastMessage = messages[messages.length - 1];
18
+ if (!(lastMessage instanceof ToolMessage))
19
+ return [];
20
+ // Find the latest AIMessage with tool_calls that this tool message belongs to
21
+ const latestAIParentIndex = findLastIndex(messages, (msg) => (msg instanceof AIMessageChunk &&
22
+ (msg.tool_calls?.length ?? 0) > 0 &&
23
+ msg.tool_calls?.some((tc) => tc.id === lastMessage.tool_call_id)) ??
24
+ false);
25
+ if (latestAIParentIndex === -1)
26
+ return [];
27
+ // Collect tool_call_ids from the AI message
28
+ const aiMessage = messages[latestAIParentIndex];
29
+ const toolCallIds = new Set(aiMessage.tool_calls?.map((tc) => tc.id) ?? []);
30
+ // Only process tool search results after the AI message that belong to this turn
31
+ const discoveredNames = [];
32
+ for (let i = latestAIParentIndex + 1; i < messages.length; i++) {
33
+ const msg = messages[i];
34
+ if (!(msg instanceof ToolMessage))
35
+ continue;
36
+ if (msg.name !== Constants.TOOL_SEARCH_REGEX)
37
+ continue;
38
+ if (!toolCallIds.has(msg.tool_call_id))
39
+ continue;
40
+ // This is a tool search result from the current turn
41
+ if (typeof msg.artifact === 'object' && msg.artifact != null) {
42
+ const artifact = msg.artifact;
43
+ if (artifact.tool_references && artifact.tool_references.length > 0) {
44
+ for (const ref of artifact.tool_references) {
45
+ discoveredNames.push(ref.tool_name);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return discoveredNames;
51
+ }
52
+ /**
53
+ * Checks if the current turn has any tool search results.
54
+ * Quick check to avoid full extraction when not needed.
55
+ */
56
+ function hasToolSearchInCurrentTurn(messages) {
57
+ const lastMessage = messages[messages.length - 1];
58
+ if (!(lastMessage instanceof ToolMessage))
59
+ return false;
60
+ // Find the latest AIMessage with tool_calls
61
+ const latestAIParentIndex = findLastIndex(messages, (msg) => (msg instanceof AIMessageChunk &&
62
+ (msg.tool_calls?.length ?? 0) > 0 &&
63
+ msg.tool_calls?.some((tc) => tc.id === lastMessage.tool_call_id)) ??
64
+ false);
65
+ if (latestAIParentIndex === -1)
66
+ return false;
67
+ const aiMessage = messages[latestAIParentIndex];
68
+ const toolCallIds = new Set(aiMessage.tool_calls?.map((tc) => tc.id) ?? []);
69
+ // Check if any tool search results exist after the AI message
70
+ for (let i = latestAIParentIndex + 1; i < messages.length; i++) {
71
+ const msg = messages[i];
72
+ if (msg instanceof ToolMessage &&
73
+ msg.name === Constants.TOOL_SEARCH_REGEX &&
74
+ toolCallIds.has(msg.tool_call_id)) {
75
+ return true;
76
+ }
77
+ }
78
+ return false;
79
+ }
80
+
81
+ export { extractToolDiscoveries, hasToolSearchInCurrentTurn };
82
+ //# sourceMappingURL=tools.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.mjs","sources":["../../../src/messages/tools.ts"],"sourcesContent":["// src/messages/toolDiscovery.ts\nimport { AIMessageChunk, ToolMessage } from '@langchain/core/messages';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { Constants } from '@/common';\nimport { findLastIndex } from './core';\n\ntype ToolSearchArtifact = {\n tool_references?: Array<{ tool_name: string }>;\n};\n\n/**\n * Extracts discovered tool names from tool search results in the current turn.\n * Only processes tool search messages after the latest AI message with tool calls.\n *\n * Similar pattern to formatArtifactPayload - finds relevant messages efficiently\n * by identifying the latest AI parent and only processing subsequent tool messages.\n *\n * @param messages - All messages in the conversation\n * @returns Array of discovered tool names (empty if no new discoveries)\n */\nexport function extractToolDiscoveries(messages: BaseMessage[]): string[] {\n const lastMessage = messages[messages.length - 1];\n if (!(lastMessage instanceof ToolMessage)) return [];\n\n // Find the latest AIMessage with tool_calls that this tool message belongs to\n const latestAIParentIndex = findLastIndex(\n messages,\n (msg) =>\n (msg instanceof AIMessageChunk &&\n (msg.tool_calls?.length ?? 0) > 0 &&\n msg.tool_calls?.some((tc) => tc.id === lastMessage.tool_call_id)) ??\n false\n );\n\n if (latestAIParentIndex === -1) return [];\n\n // Collect tool_call_ids from the AI message\n const aiMessage = messages[latestAIParentIndex] as AIMessageChunk;\n const toolCallIds = new Set(aiMessage.tool_calls?.map((tc) => tc.id) ?? []);\n\n // Only process tool search results after the AI message that belong to this turn\n const discoveredNames: string[] = [];\n for (let i = latestAIParentIndex + 1; i < messages.length; i++) {\n const msg = messages[i];\n if (!(msg instanceof ToolMessage)) continue;\n if (msg.name !== Constants.TOOL_SEARCH_REGEX) continue;\n if (!toolCallIds.has(msg.tool_call_id)) continue;\n\n // This is a tool search result from the current turn\n if (typeof msg.artifact === 'object' && msg.artifact != null) {\n const artifact = msg.artifact as ToolSearchArtifact;\n if (artifact.tool_references && artifact.tool_references.length > 0) {\n for (const ref of artifact.tool_references) {\n discoveredNames.push(ref.tool_name);\n }\n }\n }\n }\n\n return discoveredNames;\n}\n\n/**\n * Checks if the current turn has any tool search results.\n * Quick check to avoid full extraction when not needed.\n */\nexport function hasToolSearchInCurrentTurn(messages: BaseMessage[]): boolean {\n const lastMessage = messages[messages.length - 1];\n if (!(lastMessage instanceof ToolMessage)) return false;\n\n // Find the latest AIMessage with tool_calls\n const latestAIParentIndex = findLastIndex(\n messages,\n (msg) =>\n (msg instanceof AIMessageChunk &&\n (msg.tool_calls?.length ?? 0) > 0 &&\n msg.tool_calls?.some((tc) => tc.id === lastMessage.tool_call_id)) ??\n false\n );\n\n if (latestAIParentIndex === -1) return false;\n\n const aiMessage = messages[latestAIParentIndex] as AIMessageChunk;\n const toolCallIds = new Set(aiMessage.tool_calls?.map((tc) => tc.id) ?? []);\n\n // Check if any tool search results exist after the AI message\n for (let i = latestAIParentIndex + 1; i < messages.length; i++) {\n const msg = messages[i];\n if (\n msg instanceof ToolMessage &&\n msg.name === Constants.TOOL_SEARCH_REGEX &&\n toolCallIds.has(msg.tool_call_id)\n ) {\n return true;\n }\n }\n\n return false;\n}\n"],"names":[],"mappings":"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"}
package/dist/esm/run.mjs CHANGED
@@ -204,14 +204,6 @@ class Run {
204
204
  for await (const event of stream) {
205
205
  const { data, metadata, ...info } = event;
206
206
  const eventName = info.event;
207
- // Debug: log tool-related events
208
- if (eventName.includes('tool')) {
209
- console.log('[AgentRun] Tool event received:', eventName, {
210
- hasData: !!data,
211
- dataKeys: data ? Object.keys(data) : [],
212
- outputType: data?.output ? typeof data.output : 'none',
213
- });
214
- }
215
207
  /** Skip custom events as they're handled by our callback */
216
208
  if (eventName === GraphEvents.ON_CUSTOM_EVENT) {
217
209
  continue;