illuma-agents 1.0.38 → 1.0.40

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 (46) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +45 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +2 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +110 -1
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +6 -0
  8. package/dist/cjs/main.cjs.map +1 -1
  9. package/dist/cjs/messages/cache.cjs +140 -47
  10. package/dist/cjs/messages/cache.cjs.map +1 -1
  11. package/dist/cjs/schemas/validate.cjs +173 -0
  12. package/dist/cjs/schemas/validate.cjs.map +1 -0
  13. package/dist/esm/agents/AgentContext.mjs +45 -2
  14. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  15. package/dist/esm/common/enum.mjs +2 -0
  16. package/dist/esm/common/enum.mjs.map +1 -1
  17. package/dist/esm/graphs/Graph.mjs +110 -1
  18. package/dist/esm/graphs/Graph.mjs.map +1 -1
  19. package/dist/esm/main.mjs +1 -0
  20. package/dist/esm/main.mjs.map +1 -1
  21. package/dist/esm/messages/cache.mjs +140 -47
  22. package/dist/esm/messages/cache.mjs.map +1 -1
  23. package/dist/esm/schemas/validate.mjs +167 -0
  24. package/dist/esm/schemas/validate.mjs.map +1 -0
  25. package/dist/types/agents/AgentContext.d.ts +19 -1
  26. package/dist/types/common/enum.d.ts +2 -0
  27. package/dist/types/graphs/Graph.d.ts +6 -0
  28. package/dist/types/index.d.ts +1 -0
  29. package/dist/types/messages/cache.d.ts +4 -1
  30. package/dist/types/schemas/index.d.ts +1 -0
  31. package/dist/types/schemas/validate.d.ts +36 -0
  32. package/dist/types/types/graph.d.ts +69 -0
  33. package/package.json +2 -2
  34. package/src/agents/AgentContext.test.ts +312 -0
  35. package/src/agents/AgentContext.ts +56 -0
  36. package/src/common/enum.ts +2 -0
  37. package/src/graphs/Graph.ts +166 -2
  38. package/src/index.ts +3 -0
  39. package/src/messages/cache.test.ts +51 -6
  40. package/src/messages/cache.ts +149 -122
  41. package/src/schemas/index.ts +2 -0
  42. package/src/schemas/validate.test.ts +358 -0
  43. package/src/schemas/validate.ts +238 -0
  44. package/src/specs/cache.simple.test.ts +396 -0
  45. package/src/types/graph.test.ts +183 -0
  46. package/src/types/graph.ts +71 -0
@@ -1,4 +1,3 @@
1
- import '@langchain/core/messages';
2
1
  import { ContentTypes } from '../common/enum.mjs';
3
2
 
4
3
  /** Debug logger for cache operations - set ILLUMA_DEBUG_CACHE=true to enable */
@@ -8,13 +7,97 @@ const debugCache = (message, data) => {
8
7
  console.log(`[Cache] ${message}`, data !== undefined ? JSON.stringify(data, null, 2) : '');
9
8
  }
10
9
  };
10
+ /**
11
+ * Deep clones a message's content to prevent mutation of the original.
12
+ */
13
+ function deepCloneContent(content) {
14
+ if (typeof content === 'string') {
15
+ return content;
16
+ }
17
+ if (Array.isArray(content)) {
18
+ return content.map((block) => ({ ...block }));
19
+ }
20
+ return content;
21
+ }
22
+ /**
23
+ * Clones a message with deep-cloned content, explicitly excluding LangChain
24
+ * serialization metadata to prevent coercion issues.
25
+ * Keeps lc_kwargs in sync with content to prevent LangChain serialization issues.
26
+ */
27
+ function cloneMessage(message, content) {
28
+ const { lc_kwargs: _lc_kwargs, lc_serializable: _lc_serializable, lc_namespace: _lc_namespace, ...rest } = message;
29
+ const cloned = { ...rest, content };
30
+ // Sync lc_kwargs.content with the new content to prevent LangChain coercion issues
31
+ const lcKwargs = message.lc_kwargs;
32
+ if (lcKwargs != null) {
33
+ cloned.lc_kwargs = {
34
+ ...lcKwargs,
35
+ content: content,
36
+ };
37
+ }
38
+ // LangChain messages don't have a direct 'role' property - derive it from getType()
39
+ if ('getType' in message &&
40
+ typeof message.getType === 'function' &&
41
+ !('role' in cloned)) {
42
+ const msgType = message.getType();
43
+ const roleMap = {
44
+ human: 'user',
45
+ ai: 'assistant',
46
+ system: 'system',
47
+ tool: 'tool',
48
+ };
49
+ cloned.role = roleMap[msgType] || msgType;
50
+ }
51
+ return cloned;
52
+ }
53
+ /**
54
+ * Checks if a content block is a cache point
55
+ */
56
+ function isCachePoint(block) {
57
+ return 'cachePoint' in block && !('type' in block);
58
+ }
59
+ /**
60
+ * Checks if a message's content needs cache control stripping.
61
+ * Returns true if content has cachePoint blocks or cache_control fields.
62
+ */
63
+ function needsCacheStripping(content) {
64
+ for (let i = 0; i < content.length; i++) {
65
+ const block = content[i];
66
+ if (isCachePoint(block))
67
+ return true;
68
+ if ('cache_control' in block)
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+ /**
74
+ * Checks if a message's content has Anthropic cache_control fields.
75
+ */
76
+ function hasAnthropicCacheControl(content) {
77
+ for (let i = 0; i < content.length; i++) {
78
+ if ('cache_control' in content[i])
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+ /**
84
+ * Checks if a message's content has Bedrock cachePoint blocks.
85
+ */
86
+ function hasBedrockCachePoint(content) {
87
+ for (let i = 0; i < content.length; i++) {
88
+ if (isCachePoint(content[i]))
89
+ return true;
90
+ }
91
+ return false;
92
+ }
11
93
  /**
12
94
  * Anthropic API: Adds cache control to the appropriate user messages in the payload.
13
95
  * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
14
96
  * then adds fresh cache control to the last 2 user messages in a single backward pass.
15
97
  * This ensures we don't accumulate stale cache points across multiple turns.
98
+ * Returns a new array - only clones messages that require modification.
16
99
  * @param messages - The array of message objects.
17
- * @returns - The updated array of message objects with cache control added.
100
+ * @returns - A new array of message objects with cache control added.
18
101
  */
19
102
  function addCacheControl(messages) {
20
103
  if (!Array.isArray(messages) || messages.length < 2) {
@@ -23,55 +106,59 @@ function addCacheControl(messages) {
23
106
  const updatedMessages = [...messages];
24
107
  let userMessagesModified = 0;
25
108
  for (let i = updatedMessages.length - 1; i >= 0; i--) {
26
- const message = updatedMessages[i];
27
- const isUserMessage = ('getType' in message && message.getType() === 'human') ||
28
- ('role' in message && message.role === 'user');
29
- if (Array.isArray(message.content)) {
30
- message.content = message.content.filter((block) => !isCachePoint(block));
31
- for (let j = 0; j < message.content.length; j++) {
32
- const block = message.content[j];
109
+ const originalMessage = updatedMessages[i];
110
+ const content = originalMessage.content;
111
+ const isUserMessage = ('getType' in originalMessage && originalMessage.getType() === 'human') ||
112
+ ('role' in originalMessage && originalMessage.role === 'user');
113
+ const hasArrayContent = Array.isArray(content);
114
+ const needsStripping = hasArrayContent &&
115
+ needsCacheStripping(content);
116
+ const needsCacheAdd = userMessagesModified < 2 &&
117
+ isUserMessage &&
118
+ (typeof content === 'string' || hasArrayContent);
119
+ if (!needsStripping && !needsCacheAdd) {
120
+ continue;
121
+ }
122
+ let workingContent;
123
+ if (hasArrayContent) {
124
+ workingContent = deepCloneContent(content).filter((block) => !isCachePoint(block));
125
+ for (let j = 0; j < workingContent.length; j++) {
126
+ const block = workingContent[j];
33
127
  if ('cache_control' in block) {
34
128
  delete block.cache_control;
35
129
  }
36
130
  }
37
131
  }
132
+ else if (typeof content === 'string') {
133
+ workingContent = [
134
+ { type: 'text', text: content },
135
+ ];
136
+ }
137
+ else {
138
+ workingContent = [];
139
+ }
38
140
  if (userMessagesModified >= 2 || !isUserMessage) {
141
+ updatedMessages[i] = cloneMessage(originalMessage, workingContent);
39
142
  continue;
40
143
  }
41
- if (typeof message.content === 'string') {
42
- message.content = [
43
- {
44
- type: 'text',
45
- text: message.content,
46
- cache_control: { type: 'ephemeral' },
47
- },
48
- ];
49
- userMessagesModified++;
50
- }
51
- else if (Array.isArray(message.content)) {
52
- for (let j = message.content.length - 1; j >= 0; j--) {
53
- const contentPart = message.content[j];
54
- if ('type' in contentPart && contentPart.type === 'text') {
55
- contentPart.cache_control = {
56
- type: 'ephemeral',
57
- };
58
- userMessagesModified++;
59
- break;
60
- }
144
+ for (let j = workingContent.length - 1; j >= 0; j--) {
145
+ const contentPart = workingContent[j];
146
+ if ('type' in contentPart && contentPart.type === 'text') {
147
+ contentPart.cache_control = {
148
+ type: 'ephemeral',
149
+ };
150
+ userMessagesModified++;
151
+ break;
61
152
  }
62
153
  }
154
+ updatedMessages[i] = cloneMessage(originalMessage, workingContent);
63
155
  }
64
156
  return updatedMessages;
65
157
  }
66
- /**
67
- * Checks if a content block is a cache point
68
- */
69
- function isCachePoint(block) {
70
- return 'cachePoint' in block && !('type' in block);
71
- }
72
158
  /**
73
159
  * Removes all Anthropic cache_control fields from messages
74
160
  * Used when switching from Anthropic to Bedrock provider
161
+ * Returns a new array - only clones messages that require modification.
75
162
  */
76
163
  function stripAnthropicCacheControl(messages) {
77
164
  if (!Array.isArray(messages)) {
@@ -79,22 +166,26 @@ function stripAnthropicCacheControl(messages) {
79
166
  }
80
167
  const updatedMessages = [...messages];
81
168
  for (let i = 0; i < updatedMessages.length; i++) {
82
- const message = updatedMessages[i];
83
- const content = message.content;
84
- if (Array.isArray(content)) {
85
- for (let j = 0; j < content.length; j++) {
86
- const block = content[j];
87
- if ('cache_control' in block) {
88
- delete block.cache_control;
89
- }
169
+ const originalMessage = updatedMessages[i];
170
+ const content = originalMessage.content;
171
+ if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {
172
+ continue;
173
+ }
174
+ const clonedContent = deepCloneContent(content);
175
+ for (let j = 0; j < clonedContent.length; j++) {
176
+ const block = clonedContent[j];
177
+ if ('cache_control' in block) {
178
+ delete block.cache_control;
90
179
  }
91
180
  }
181
+ updatedMessages[i] = cloneMessage(originalMessage, clonedContent);
92
182
  }
93
183
  return updatedMessages;
94
184
  }
95
185
  /**
96
186
  * Removes all Bedrock cachePoint blocks from messages
97
187
  * Used when switching from Bedrock to Anthropic provider
188
+ * Returns a new array - only clones messages that require modification.
98
189
  */
99
190
  function stripBedrockCacheControl(messages) {
100
191
  if (!Array.isArray(messages)) {
@@ -102,11 +193,13 @@ function stripBedrockCacheControl(messages) {
102
193
  }
103
194
  const updatedMessages = [...messages];
104
195
  for (let i = 0; i < updatedMessages.length; i++) {
105
- const message = updatedMessages[i];
106
- const content = message.content;
107
- if (Array.isArray(content)) {
108
- message.content = content.filter((block) => !isCachePoint(block));
196
+ const originalMessage = updatedMessages[i];
197
+ const content = originalMessage.content;
198
+ if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {
199
+ continue;
109
200
  }
201
+ const clonedContent = deepCloneContent(content).filter((block) => !isCachePoint(block));
202
+ updatedMessages[i] = cloneMessage(originalMessage, clonedContent);
110
203
  }
111
204
  return updatedMessages;
112
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cache.mjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import {\n BaseMessage,\n MessageContentComplex,\n AIMessage,\n HumanMessage,\n SystemMessage,\n ToolMessage,\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 * Simple shallow clone with deep-cloned content.\n * Used for stripping cache control where we don't need proper LangChain instances.\n */\nfunction _shallowCloneMessage<T extends MessageWithContent>(message: T): T {\n const cloned = {\n ...message,\n content: deepCloneContent(message.content ?? ''),\n } as T;\n const lcKwargs = (cloned 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: cloned.content,\n };\n }\n return cloned;\n}\n\n/**\n * Creates a new LangChain message instance with the given content.\n * Required when adding cache points to ensure proper serialization.\n */\nfunction _createNewMessage<T extends MessageWithContent>(\n message: T,\n content: MessageContentComplex[]\n): T {\n if ('getType' in message && typeof message.getType === 'function') {\n const baseMsg = message as unknown as BaseMessage;\n const msgType = baseMsg.getType();\n\n const baseFields = {\n content,\n name: baseMsg.name,\n additional_kwargs: { ...baseMsg.additional_kwargs },\n response_metadata: { ...baseMsg.response_metadata },\n id: baseMsg.id,\n };\n\n switch (msgType) {\n case 'human':\n return new HumanMessage(baseFields) as unknown as T;\n case 'ai': {\n const aiMsg = baseMsg as AIMessage;\n return new AIMessage({\n ...baseFields,\n tool_calls: aiMsg.tool_calls ? [...aiMsg.tool_calls] : [],\n invalid_tool_calls: aiMsg.invalid_tool_calls\n ? [...aiMsg.invalid_tool_calls]\n : [],\n usage_metadata: aiMsg.usage_metadata,\n }) as unknown as T;\n }\n case 'system':\n return new SystemMessage(baseFields) as unknown as T;\n case 'tool': {\n const toolMsg = baseMsg as ToolMessage;\n return new ToolMessage({\n ...baseFields,\n tool_call_id: toolMsg.tool_call_id,\n status: toolMsg.status,\n artifact: toolMsg.artifact,\n }) as unknown as T;\n }\n default:\n break;\n }\n }\n\n const cloned = { ...message, content } as T;\n const lcKwargs = (cloned 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 = { ...lcKwargs, content };\n }\n return cloned;\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', {\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 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 =\n '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 =\n '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(\n 'addBedrockCacheControl: Skipping assistant message with reasoning block',\n { index: i }\n );\n continue;\n }\n lastAssistantIndex = i;\n break;\n }\n }\n\n // Log message summary\n debugCache(\n `📨 Messages | total=${updatedMessages.length} | ${Object.entries(\n messageTypes\n )\n .map(([k, v]) => `${k}:${v}`)\n .join(' ')} | skippedReasoning=${skippedWithReasoning}`\n );\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 debugCache(\n '📨 Messages | No suitable assistant message for cachePoint (first turn or all have reasoning)'\n );\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 debugCache(\n `📍 Message cachePoint at index ${lastAssistantIndex} (string, ${content.length} chars)`\n );\n debugCache(\n 'addBedrockCacheControl: Added cachePoint to assistant message (string content)',\n {\n index: lastAssistantIndex,\n contentLength: content.length,\n }\n );\n } else if (\n Array.isArray(assistantMessage.content) &&\n assistantMessage.content.length > 0\n ) {\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 debugCache(\n `⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (has reasoning blocks)`\n );\n debugCache(\n 'addBedrockCacheControl: Skipping - assistant message has reasoning blocks (safety check)',\n {\n index: lastAssistantIndex,\n }\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 != null && text !== '') {\n assistantMessage.content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n debugCache(\n `📍 Message cachePoint at index ${lastAssistantIndex} (array, block ${j}, ${text.length} chars)`\n );\n debugCache(\n 'addBedrockCacheControl: Added cachePoint after text block in assistant message',\n {\n index: lastAssistantIndex,\n textBlockIndex: j,\n contentLength: text.length,\n }\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(\n (b) => (b as { type?: string }).type\n );\n debugCache(\n `⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (no text block, types: ${contentTypes.join(',')})`\n );\n debugCache(\n 'addBedrockCacheControl: No suitable text block found, skipping cache point',\n {\n index: lastAssistantIndex,\n contentTypes,\n }\n );\n }\n }\n\n debugCache(\n 'addBedrockCacheControl: Complete - stable prefix caching applied',\n {\n lastAssistantIndex,\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 { 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/** 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 deep-cloned content, explicitly excluding LangChain\n * serialization metadata to prevent coercion issues.\n * Keeps lc_kwargs in sync with content to prevent LangChain serialization issues.\n */\nfunction cloneMessage<T extends MessageWithContent>(\n message: T,\n content: string | MessageContentComplex[]\n): T {\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 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', {\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 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 =\n '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 =\n '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(\n 'addBedrockCacheControl: Skipping assistant message with reasoning block',\n { index: i }\n );\n continue;\n }\n lastAssistantIndex = i;\n break;\n }\n }\n\n // Log message summary\n debugCache(\n `📨 Messages | total=${updatedMessages.length} | ${Object.entries(\n messageTypes\n )\n .map(([k, v]) => `${k}:${v}`)\n .join(' ')} | skippedReasoning=${skippedWithReasoning}`\n );\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 debugCache(\n '📨 Messages | No suitable assistant message for cachePoint (first turn or all have reasoning)'\n );\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 debugCache(\n `📍 Message cachePoint at index ${lastAssistantIndex} (string, ${content.length} chars)`\n );\n debugCache(\n 'addBedrockCacheControl: Added cachePoint to assistant message (string content)',\n {\n index: lastAssistantIndex,\n contentLength: content.length,\n }\n );\n } else if (\n Array.isArray(assistantMessage.content) &&\n assistantMessage.content.length > 0\n ) {\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 debugCache(\n `⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (has reasoning blocks)`\n );\n debugCache(\n 'addBedrockCacheControl: Skipping - assistant message has reasoning blocks (safety check)',\n {\n index: lastAssistantIndex,\n }\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 != null && text !== '') {\n assistantMessage.content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n debugCache(\n `📍 Message cachePoint at index ${lastAssistantIndex} (array, block ${j}, ${text.length} chars)`\n );\n debugCache(\n 'addBedrockCacheControl: Added cachePoint after text block in assistant message',\n {\n index: lastAssistantIndex,\n textBlockIndex: j,\n contentLength: text.length,\n }\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(\n (b) => (b as { type?: string }).type\n );\n debugCache(\n `⚠️ Message cachePoint SKIPPED at index ${lastAssistantIndex} (no text block, types: ${contentTypes.join(',')})`\n );\n debugCache(\n 'addBedrockCacheControl: No suitable text block found, skipping cache point',\n {\n index: lastAssistantIndex,\n contentTypes,\n }\n );\n }\n }\n\n debugCache(\n 'addBedrockCacheControl: Complete - stable prefix caching applied',\n {\n lastAssistantIndex,\n totalMessages: updatedMessages.length,\n }\n );\n\n return updatedMessages;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Validates structured output against a JSON Schema.
3
+ *
4
+ * @param output - The output to validate
5
+ * @param schema - JSON Schema to validate against
6
+ * @returns Validation result with success flag and data or error
7
+ */
8
+ function validateStructuredOutput(output, schema) {
9
+ try {
10
+ // Parse output if it's a string
11
+ const data = typeof output === 'string' ? JSON.parse(output) : output;
12
+ // Basic JSON Schema validation
13
+ const errors = validateAgainstJsonSchema(data, schema);
14
+ if (errors.length > 0) {
15
+ return {
16
+ success: false,
17
+ error: `Validation failed: ${errors.join('; ')}`,
18
+ raw: data,
19
+ };
20
+ }
21
+ return {
22
+ success: true,
23
+ data: data,
24
+ };
25
+ }
26
+ catch (e) {
27
+ return {
28
+ success: false,
29
+ error: `Failed to parse output: ${e.message}`,
30
+ raw: output,
31
+ };
32
+ }
33
+ }
34
+ /**
35
+ * Validates data against a JSON Schema (simplified implementation).
36
+ * For complex schemas, consider using ajv or similar library.
37
+ */
38
+ function validateAgainstJsonSchema(data, schema, path = '') {
39
+ const errors = [];
40
+ if (schema.type === 'object' && typeof data !== 'object') {
41
+ errors.push(`${path || 'root'}: expected object, got ${typeof data}`);
42
+ return errors;
43
+ }
44
+ if (schema.type === 'array' && !Array.isArray(data)) {
45
+ errors.push(`${path || 'root'}: expected array, got ${typeof data}`);
46
+ return errors;
47
+ }
48
+ if (schema.type === 'string' && typeof data !== 'string') {
49
+ errors.push(`${path || 'root'}: expected string, got ${typeof data}`);
50
+ return errors;
51
+ }
52
+ if (schema.type === 'number' && typeof data !== 'number') {
53
+ errors.push(`${path || 'root'}: expected number, got ${typeof data}`);
54
+ return errors;
55
+ }
56
+ if (schema.type === 'boolean' && typeof data !== 'boolean') {
57
+ errors.push(`${path || 'root'}: expected boolean, got ${typeof data}`);
58
+ return errors;
59
+ }
60
+ // Validate required properties
61
+ if (schema.type === 'object' &&
62
+ Array.isArray(schema.required) &&
63
+ typeof data === 'object' &&
64
+ data !== null) {
65
+ for (const requiredProp of schema.required) {
66
+ if (!(requiredProp in data)) {
67
+ errors.push(`${path || 'root'}: missing required property '${requiredProp}'`);
68
+ }
69
+ }
70
+ }
71
+ // Validate nested properties
72
+ if (schema.type === 'object' &&
73
+ schema.properties &&
74
+ typeof data === 'object' &&
75
+ data !== null) {
76
+ const properties = schema.properties;
77
+ for (const [key, propSchema] of Object.entries(properties)) {
78
+ if (key in data) {
79
+ const nestedErrors = validateAgainstJsonSchema(data[key], propSchema, path ? `${path}.${key}` : key);
80
+ errors.push(...nestedErrors);
81
+ }
82
+ }
83
+ }
84
+ // Validate array items
85
+ if (schema.type === 'array' && schema.items && Array.isArray(data)) {
86
+ const itemSchema = schema.items;
87
+ for (let i = 0; i < data.length; i++) {
88
+ const nestedErrors = validateAgainstJsonSchema(data[i], itemSchema, `${path || 'root'}[${i}]`);
89
+ errors.push(...nestedErrors);
90
+ }
91
+ }
92
+ // Validate enum values
93
+ if (schema.enum && Array.isArray(schema.enum)) {
94
+ if (!schema.enum.includes(data)) {
95
+ errors.push(`${path || 'root'}: value '${data}' not in enum [${schema.enum.join(', ')}]`);
96
+ }
97
+ }
98
+ return errors;
99
+ }
100
+ /**
101
+ * Converts a Zod schema to JSON Schema format.
102
+ * This is a simplified converter for common types.
103
+ */
104
+ function zodToJsonSchema(zodSchema) {
105
+ // Use the zod-to-json-schema library if available
106
+ // For now, provide a basic implementation
107
+ try {
108
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
109
+ const { zodToJsonSchema: convert } = require('zod-to-json-schema');
110
+ return convert(zodSchema);
111
+ }
112
+ catch {
113
+ // Fallback: return a generic object schema
114
+ return {
115
+ type: 'object',
116
+ additionalProperties: true,
117
+ };
118
+ }
119
+ }
120
+ /**
121
+ * Creates a structured output error message for retry.
122
+ */
123
+ function createValidationErrorMessage(result, customMessage) {
124
+ if (customMessage) {
125
+ return customMessage;
126
+ }
127
+ return `The response did not match the expected schema. Error: ${result.error}. Please try again and ensure your response exactly matches the required format.`;
128
+ }
129
+ /**
130
+ * Checks if a value is a valid JSON Schema object.
131
+ */
132
+ function isValidJsonSchema(value) {
133
+ if (typeof value !== 'object' || value === null) {
134
+ return false;
135
+ }
136
+ const schema = value;
137
+ // Basic check: must have a type or properties
138
+ return (typeof schema.type === 'string' ||
139
+ typeof schema.properties === 'object' ||
140
+ typeof schema.$schema === 'string');
141
+ }
142
+ /**
143
+ * Normalizes a JSON Schema by adding defaults and cleaning up.
144
+ */
145
+ function normalizeJsonSchema(schema, config) {
146
+ const normalized = { ...schema };
147
+ // Ensure type is set
148
+ if (!normalized.type && normalized.properties) {
149
+ normalized.type = 'object';
150
+ }
151
+ // Add title from config name
152
+ if (config?.name && !normalized.title) {
153
+ normalized.title = config.name;
154
+ }
155
+ // Add description from config
156
+ if (config?.description && !normalized.description) {
157
+ normalized.description = config.description;
158
+ }
159
+ // Enable additionalProperties: false for strict mode
160
+ if (config?.strict !== false && normalized.type === 'object') {
161
+ normalized.additionalProperties = normalized.additionalProperties ?? false;
162
+ }
163
+ return normalized;
164
+ }
165
+
166
+ export { createValidationErrorMessage, isValidJsonSchema, normalizeJsonSchema, validateStructuredOutput, zodToJsonSchema };
167
+ //# sourceMappingURL=validate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.mjs","sources":["../../../src/schemas/validate.ts"],"sourcesContent":["// src/schemas/validate.ts\nimport { z } from 'zod';\nimport type * as t from '@/types';\n\n/**\n * Validation result from structured output\n */\nexport interface ValidationResult<T = Record<string, unknown>> {\n success: boolean;\n data?: T;\n error?: string;\n raw?: unknown;\n}\n\n/**\n * Validates structured output against a JSON Schema.\n *\n * @param output - The output to validate\n * @param schema - JSON Schema to validate against\n * @returns Validation result with success flag and data or error\n */\nexport function validateStructuredOutput(\n output: unknown,\n schema: Record<string, unknown>\n): ValidationResult {\n try {\n // Parse output if it's a string\n const data = typeof output === 'string' ? JSON.parse(output) : output;\n\n // Basic JSON Schema validation\n const errors = validateAgainstJsonSchema(data, schema);\n\n if (errors.length > 0) {\n return {\n success: false,\n error: `Validation failed: ${errors.join('; ')}`,\n raw: data,\n };\n }\n\n return {\n success: true,\n data: data as Record<string, unknown>,\n };\n } catch (e) {\n return {\n success: false,\n error: `Failed to parse output: ${(e as Error).message}`,\n raw: output,\n };\n }\n}\n\n/**\n * Validates data against a JSON Schema (simplified implementation).\n * For complex schemas, consider using ajv or similar library.\n */\nfunction validateAgainstJsonSchema(\n data: unknown,\n schema: Record<string, unknown>,\n path = ''\n): string[] {\n const errors: string[] = [];\n\n if (schema.type === 'object' && typeof data !== 'object') {\n errors.push(`${path || 'root'}: expected object, got ${typeof data}`);\n return errors;\n }\n\n if (schema.type === 'array' && !Array.isArray(data)) {\n errors.push(`${path || 'root'}: expected array, got ${typeof data}`);\n return errors;\n }\n\n if (schema.type === 'string' && typeof data !== 'string') {\n errors.push(`${path || 'root'}: expected string, got ${typeof data}`);\n return errors;\n }\n\n if (schema.type === 'number' && typeof data !== 'number') {\n errors.push(`${path || 'root'}: expected number, got ${typeof data}`);\n return errors;\n }\n\n if (schema.type === 'boolean' && typeof data !== 'boolean') {\n errors.push(`${path || 'root'}: expected boolean, got ${typeof data}`);\n return errors;\n }\n\n // Validate required properties\n if (\n schema.type === 'object' &&\n Array.isArray(schema.required) &&\n typeof data === 'object' &&\n data !== null\n ) {\n for (const requiredProp of schema.required as string[]) {\n if (!(requiredProp in data)) {\n errors.push(\n `${path || 'root'}: missing required property '${requiredProp}'`\n );\n }\n }\n }\n\n // Validate nested properties\n if (\n schema.type === 'object' &&\n schema.properties &&\n typeof data === 'object' &&\n data !== null\n ) {\n const properties = schema.properties as Record<\n string,\n Record<string, unknown>\n >;\n for (const [key, propSchema] of Object.entries(properties)) {\n if (key in data) {\n const nestedErrors = validateAgainstJsonSchema(\n (data as Record<string, unknown>)[key],\n propSchema,\n path ? `${path}.${key}` : key\n );\n errors.push(...nestedErrors);\n }\n }\n }\n\n // Validate array items\n if (schema.type === 'array' && schema.items && Array.isArray(data)) {\n const itemSchema = schema.items as Record<string, unknown>;\n for (let i = 0; i < data.length; i++) {\n const nestedErrors = validateAgainstJsonSchema(\n data[i],\n itemSchema,\n `${path || 'root'}[${i}]`\n );\n errors.push(...nestedErrors);\n }\n }\n\n // Validate enum values\n if (schema.enum && Array.isArray(schema.enum)) {\n if (!schema.enum.includes(data)) {\n errors.push(\n `${path || 'root'}: value '${data}' not in enum [${(schema.enum as unknown[]).join(', ')}]`\n );\n }\n }\n\n return errors;\n}\n\n/**\n * Converts a Zod schema to JSON Schema format.\n * This is a simplified converter for common types.\n */\nexport function zodToJsonSchema(zodSchema: z.ZodType): Record<string, unknown> {\n // Use the zod-to-json-schema library if available\n // For now, provide a basic implementation\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { zodToJsonSchema: convert } = require('zod-to-json-schema');\n return convert(zodSchema) as Record<string, unknown>;\n } catch {\n // Fallback: return a generic object schema\n return {\n type: 'object',\n additionalProperties: true,\n };\n }\n}\n\n/**\n * Creates a structured output error message for retry.\n */\nexport function createValidationErrorMessage(\n result: ValidationResult,\n customMessage?: string\n): string {\n if (customMessage) {\n return customMessage;\n }\n\n return `The response did not match the expected schema. Error: ${result.error}. Please try again and ensure your response exactly matches the required format.`;\n}\n\n/**\n * Checks if a value is a valid JSON Schema object.\n */\nexport function isValidJsonSchema(\n value: unknown\n): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n\n const schema = value as Record<string, unknown>;\n\n // Basic check: must have a type or properties\n return (\n typeof schema.type === 'string' ||\n typeof schema.properties === 'object' ||\n typeof schema.$schema === 'string'\n );\n}\n\n/**\n * Normalizes a JSON Schema by adding defaults and cleaning up.\n */\nexport function normalizeJsonSchema(\n schema: Record<string, unknown>,\n config?: t.StructuredOutputConfig\n): Record<string, unknown> {\n const normalized = { ...schema };\n\n // Ensure type is set\n if (!normalized.type && normalized.properties) {\n normalized.type = 'object';\n }\n\n // Add title from config name\n if (config?.name && !normalized.title) {\n normalized.title = config.name;\n }\n\n // Add description from config\n if (config?.description && !normalized.description) {\n normalized.description = config.description;\n }\n\n // Enable additionalProperties: false for strict mode\n if (config?.strict !== false && normalized.type === 'object') {\n normalized.additionalProperties = normalized.additionalProperties ?? false;\n }\n\n return normalized;\n}\n"],"names":[],"mappings":"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"}
@@ -93,7 +93,13 @@ export declare class AgentContext {
93
93
  /** Names of sibling agents executing in parallel (empty if sequential) */
94
94
  parallelSiblings: string[];
95
95
  };
96
- constructor({ agentId, name, provider, clientOptions, maxContextTokens, streamBuffer, tokenCounter, tools, toolMap, toolRegistry, instructions, additionalInstructions, dynamicContext, reasoningKey, toolEnd, instructionTokens, useLegacyContent, }: {
96
+ /**
97
+ * Structured output configuration.
98
+ * When set, the agent will return a validated JSON response
99
+ * instead of streaming text.
100
+ */
101
+ structuredOutput?: t.StructuredOutputConfig;
102
+ constructor({ agentId, name, provider, clientOptions, maxContextTokens, streamBuffer, tokenCounter, tools, toolMap, toolRegistry, instructions, additionalInstructions, dynamicContext, reasoningKey, toolEnd, instructionTokens, useLegacyContent, structuredOutput, }: {
97
103
  agentId: string;
98
104
  name?: string;
99
105
  provider: Providers;
@@ -111,7 +117,19 @@ export declare class AgentContext {
111
117
  toolEnd?: boolean;
112
118
  instructionTokens?: number;
113
119
  useLegacyContent?: boolean;
120
+ structuredOutput?: t.StructuredOutputConfig;
114
121
  });
122
+ /**
123
+ * Checks if structured output mode is enabled for this agent.
124
+ * When enabled, the agent will use model.invoke() instead of streaming
125
+ * and return a validated JSON response.
126
+ */
127
+ get isStructuredOutputMode(): boolean;
128
+ /**
129
+ * Gets the structured output schema with normalized defaults.
130
+ * Returns undefined if structured output is not configured.
131
+ */
132
+ getStructuredOutputSchema(): Record<string, unknown> | undefined;
115
133
  /**
116
134
  * Builds instructions text for tools that are ONLY callable via programmatic code execution.
117
135
  * These tools cannot be called directly by the LLM but are available through the
@@ -19,6 +19,8 @@ export declare enum GraphEvents {
19
19
  ON_REASONING_DELTA = "on_reasoning_delta",
20
20
  /** [Custom] Context analytics event for traces */
21
21
  ON_CONTEXT_ANALYTICS = "on_context_analytics",
22
+ /** [Custom] Structured output event - emitted when agent returns structured JSON */
23
+ ON_STRUCTURED_OUTPUT = "on_structured_output",
22
24
  /** Custom event, emitted by system */
23
25
  ON_CUSTOM_EVENT = "on_custom_event",
24
26
  /** Emitted when a chat model starts processing. */
@@ -151,6 +151,12 @@ export declare class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode>
151
151
  getUsageMetadata(finalMessage?: BaseMessage): Partial<UsageMetadata> | undefined;
152
152
  /** Execute model invocation with streaming support */
153
153
  private attemptInvoke;
154
+ /**
155
+ * Execute model invocation with structured output.
156
+ * Uses withStructuredOutput to force the model to return JSON conforming to the schema.
157
+ * Disables streaming and returns a validated JSON response.
158
+ */
159
+ private attemptStructuredInvoke;
154
160
  cleanupSignalListener(currentModel?: t.ChatModel): void;
155
161
  createCallModel(agentId?: string): (state: t.BaseGraphState, config?: RunnableConfig) => Promise<Partial<t.BaseGraphState>>;
156
162
  createAgentNode(agentId: string): t.CompiledAgentWorfklow;
@@ -11,6 +11,7 @@ export * from './tools/ProgrammaticToolCalling';
11
11
  export * from './tools/ToolSearch';
12
12
  export * from './tools/handlers';
13
13
  export * from './tools/search';
14
+ export * from './schemas';
14
15
  export * from './common';
15
16
  export * from './utils';
16
17
  export type * from './types';