illuma-agents 1.0.52 → 1.0.54

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.
@@ -14,6 +14,8 @@ import { findLastIndex } from './core.mjs';
14
14
  * @returns Array of discovered tool names (empty if no new discoveries)
15
15
  */
16
16
  function extractToolDiscoveries(messages) {
17
+ if (messages.length === 0)
18
+ return [];
17
19
  const lastMessage = messages[messages.length - 1];
18
20
  // Use getType() instead of instanceof to avoid module mismatch issues
19
21
  if (lastMessage.getType() !== MessageTypes.TOOL)
@@ -58,6 +60,8 @@ function extractToolDiscoveries(messages) {
58
60
  * Quick check to avoid full extraction when not needed.
59
61
  */
60
62
  function hasToolSearchInCurrentTurn(messages) {
63
+ if (messages.length === 0)
64
+ return false;
61
65
  const lastMessage = messages[messages.length - 1];
62
66
  // Use getType() instead of instanceof to avoid module mismatch issues
63
67
  if (lastMessage.getType() !== MessageTypes.TOOL)
@@ -1 +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, MessageTypes } 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 // Use getType() instead of instanceof to avoid module mismatch issues\n if (lastMessage.getType() !== MessageTypes.TOOL) return [];\n const lastToolMessage = lastMessage as ToolMessage;\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 === lastToolMessage.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 // Use getType() instead of instanceof to avoid module mismatch issues\n if (msg.getType() !== MessageTypes.TOOL) continue;\n const toolMsg = msg as ToolMessage;\n if (toolMsg.name !== Constants.TOOL_SEARCH) continue;\n if (!toolCallIds.has(toolMsg.tool_call_id)) continue;\n\n // This is a tool search result from the current turn\n if (typeof toolMsg.artifact === 'object' && toolMsg.artifact != null) {\n const artifact = toolMsg.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 // Use getType() instead of instanceof to avoid module mismatch issues\n if (lastMessage.getType() !== MessageTypes.TOOL) return false;\n const lastToolMessage = lastMessage as ToolMessage;\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 === lastToolMessage.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 // Use getType() instead of instanceof to avoid module mismatch issues\n for (let i = latestAIParentIndex + 1; i < messages.length; i++) {\n const msg = messages[i];\n if (\n msg.getType() === MessageTypes.TOOL &&\n (msg as ToolMessage).name === Constants.TOOL_SEARCH &&\n toolCallIds.has((msg as ToolMessage).tool_call_id)\n ) {\n return true;\n }\n }\n\n return false;\n}\n"],"names":[],"mappings":"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"}
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, MessageTypes } 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 if (messages.length === 0) return [];\n const lastMessage = messages[messages.length - 1];\n // Use getType() instead of instanceof to avoid module mismatch issues\n if (lastMessage.getType() !== MessageTypes.TOOL) return [];\n const lastToolMessage = lastMessage as ToolMessage;\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 === lastToolMessage.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 // Use getType() instead of instanceof to avoid module mismatch issues\n if (msg.getType() !== MessageTypes.TOOL) continue;\n const toolMsg = msg as ToolMessage;\n if (toolMsg.name !== Constants.TOOL_SEARCH) continue;\n if (!toolCallIds.has(toolMsg.tool_call_id)) continue;\n\n // This is a tool search result from the current turn\n if (typeof toolMsg.artifact === 'object' && toolMsg.artifact != null) {\n const artifact = toolMsg.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 if (messages.length === 0) return false;\n const lastMessage = messages[messages.length - 1];\n // Use getType() instead of instanceof to avoid module mismatch issues\n if (lastMessage.getType() !== MessageTypes.TOOL) return false;\n const lastToolMessage = lastMessage as ToolMessage;\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 === lastToolMessage.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 // Use getType() instead of instanceof to avoid module mismatch issues\n for (let i = latestAIParentIndex + 1; i < messages.length; i++) {\n const msg = messages[i];\n if (\n msg.getType() === MessageTypes.TOOL &&\n (msg as ToolMessage).name === Constants.TOOL_SEARCH &&\n toolCallIds.has((msg as ToolMessage).tool_call_id)\n ) {\n return true;\n }\n }\n\n return false;\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "illuma-agents",
3
- "version": "1.0.52",
3
+ "version": "1.0.54",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -764,30 +764,51 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
764
764
  }
765
765
 
766
766
  // Use withStructuredOutput to bind the schema
767
+ // Always use includeRaw: true internally so we can debug what's returned
767
768
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
768
769
  const structuredModel = (model as any).withStructuredOutput(schema, {
769
770
  name,
770
771
  method,
771
- includeRaw,
772
+ includeRaw: true, // Always true internally for debugging
772
773
  strict: structuredOutputConfig.strict !== false,
773
774
  });
774
775
 
776
+ console.log('[Graph] Structured output config:', {
777
+ name,
778
+ method,
779
+ provider,
780
+ schemaKeys: Object.keys(schema),
781
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
782
+ modelName: (model as any).model || (model as any).modelId || 'unknown',
783
+ });
784
+
775
785
  let lastError: Error | undefined;
776
786
  let attempts = 0;
777
787
 
778
788
  while (attempts <= maxRetries) {
779
789
  try {
780
790
  const result = await structuredModel.invoke(finalMessages, config);
791
+
792
+ // Debug: log what we got back
793
+ console.log('[Graph] Structured output raw result type:', typeof result);
794
+ if (result?.raw) {
795
+ const rawMsg = result.raw;
796
+ console.log('[Graph] Raw message content type:', typeof rawMsg?.content);
797
+ console.log('[Graph] Raw message tool_calls:', rawMsg?.tool_calls?.length ?? 0);
798
+ if (rawMsg?.content && typeof rawMsg.content === 'string' && rawMsg.content.length > 0) {
799
+ console.log('[Graph] Raw message text content (first 200):', rawMsg.content.substring(0, 200));
800
+ }
801
+ }
781
802
 
782
- // Handle includeRaw response format
783
- if (includeRaw && result.raw && result.parsed) {
803
+ // Handle response - we always use includeRaw internally
804
+ if (result?.raw && result?.parsed !== undefined) {
784
805
  return {
785
806
  structuredResponse: result.parsed as Record<string, unknown>,
786
807
  rawMessage: result.raw as AIMessageChunk,
787
808
  };
788
809
  }
789
810
 
790
- // Direct response
811
+ // Fallback for models that don't support includeRaw
791
812
  return {
792
813
  structuredResponse: result as Record<string, unknown>,
793
814
  };
@@ -1128,6 +1149,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1128
1149
  // Also disable thinking mode - Anthropic/Bedrock doesn't allow tool_choice with thinking enabled
1129
1150
  const structuredClientOptions = { ...agentContext.clientOptions } as t.ClientOptions;
1130
1151
 
1152
+ // CRITICAL: Disable streaming for structured output
1153
+ // Structured output uses model.invoke() with tool binding, not streaming
1154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1155
+ (structuredClientOptions as any).streaming = false;
1156
+
1131
1157
  // Remove thinking configuration for Bedrock
1132
1158
  // Bedrock uses additionalModelRequestFields.thinking for extended thinking
1133
1159
  if (agentContext.provider === Providers.BEDROCK) {
@@ -1151,8 +1177,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1151
1177
  }
1152
1178
  }
1153
1179
 
1154
- console.log('[Graph] Creating structured model for provider:', agentContext.provider, 'with options:', JSON.stringify(structuredClientOptions, null, 2));
1155
-
1156
1180
  const structuredModel = this.getNewModel({
1157
1181
  provider: agentContext.provider,
1158
1182
  clientOptions: structuredClientOptions,
@@ -101,6 +101,24 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
101
101
  this.promptCache = fields?.promptCache ?? false;
102
102
  this.applicationInferenceProfile = fields?.applicationInferenceProfile;
103
103
  this.serviceTier = fields?.serviceTier;
104
+
105
+ // Fix: Force supportsToolChoiceValues for Claude models
106
+ // The parent constructor checks `model.includes('claude-3')` but this fails when:
107
+ // 1. Using applicationInferenceProfile ARNs (arn:aws:bedrock:...)
108
+ // 2. Using different naming conventions (claude-4, claude-opus-4, etc.)
109
+ // We need to ensure tool_choice is properly set for withStructuredOutput to work
110
+ const modelName = (fields?.model ?? '').toLowerCase();
111
+ const profileName = (fields?.applicationInferenceProfile ?? '').toLowerCase();
112
+ const isClaudeModel =
113
+ modelName.includes('claude') ||
114
+ modelName.includes('anthropic') ||
115
+ profileName.includes('claude') ||
116
+ profileName.includes('anthropic');
117
+
118
+ if (isClaudeModel && !this.supportsToolChoiceValues?.length) {
119
+ // Claude models support all tool choice values
120
+ this.supportsToolChoiceValues = ['auto', 'any', 'tool'];
121
+ }
104
122
  }
105
123
 
106
124
  static lc_name(): string {
@@ -315,7 +315,7 @@ describe('CustomChatBedrockConverse', () => {
315
315
  expect(model.removeContentBlockIndex(undefined)).toBeUndefined();
316
316
  });
317
317
 
318
- test('cleanChunk should remove contentBlockIndex from AIMessageChunk response_metadata', () => {
318
+ test('processChunk should remove contentBlockIndex from AIMessageChunk response_metadata', () => {
319
319
  const model = getModelWithCleanMethods();
320
320
 
321
321
  const chunkWithIndex = new ChatGenerationChunk({
@@ -329,7 +329,7 @@ describe('CustomChatBedrockConverse', () => {
329
329
  }),
330
330
  });
331
331
 
332
- const cleaned = model.cleanChunk(chunkWithIndex);
332
+ const cleaned = model.processChunk(chunkWithIndex);
333
333
 
334
334
  expect(cleaned.message.response_metadata).toEqual({
335
335
  stopReason: null,
@@ -340,7 +340,7 @@ describe('CustomChatBedrockConverse', () => {
340
340
  expect(cleaned.text).toBe('Hello');
341
341
  });
342
342
 
343
- test('cleanChunk should pass through chunks without contentBlockIndex unchanged', () => {
343
+ test('processChunk should pass through chunks without contentBlockIndex unchanged', () => {
344
344
  const model = getModelWithCleanMethods();
345
345
 
346
346
  const chunkWithoutIndex = new ChatGenerationChunk({
@@ -354,7 +354,7 @@ describe('CustomChatBedrockConverse', () => {
354
354
  }),
355
355
  });
356
356
 
357
- const cleaned = model.cleanChunk(chunkWithoutIndex);
357
+ const cleaned = model.processChunk(chunkWithoutIndex);
358
358
 
359
359
  expect(cleaned.message.response_metadata).toEqual({
360
360
  stopReason: 'end_turn',
@@ -362,7 +362,7 @@ describe('CustomChatBedrockConverse', () => {
362
362
  });
363
363
  });
364
364
 
365
- test('cleanChunk should handle deeply nested contentBlockIndex in response_metadata', () => {
365
+ test('processChunk should handle deeply nested contentBlockIndex in response_metadata', () => {
366
366
  const model = getModelWithCleanMethods();
367
367
 
368
368
  const chunkWithNestedIndex = new ChatGenerationChunk({
@@ -381,7 +381,7 @@ describe('CustomChatBedrockConverse', () => {
381
381
  }),
382
382
  });
383
383
 
384
- const cleaned = model.cleanChunk(chunkWithNestedIndex);
384
+ const cleaned = model.processChunk(chunkWithNestedIndex);
385
385
 
386
386
  expect(cleaned.message.response_metadata).toEqual({
387
387
  amazon: {
@@ -19,6 +19,7 @@ type ToolSearchArtifact = {
19
19
  * @returns Array of discovered tool names (empty if no new discoveries)
20
20
  */
21
21
  export function extractToolDiscoveries(messages: BaseMessage[]): string[] {
22
+ if (messages.length === 0) return [];
22
23
  const lastMessage = messages[messages.length - 1];
23
24
  // Use getType() instead of instanceof to avoid module mismatch issues
24
25
  if (lastMessage.getType() !== MessageTypes.TOOL) return [];
@@ -69,6 +70,7 @@ export function extractToolDiscoveries(messages: BaseMessage[]): string[] {
69
70
  * Quick check to avoid full extraction when not needed.
70
71
  */
71
72
  export function hasToolSearchInCurrentTurn(messages: BaseMessage[]): boolean {
73
+ if (messages.length === 0) return false;
72
74
  const lastMessage = messages[messages.length - 1];
73
75
  // Use getType() instead of instanceof to avoid module mismatch issues
74
76
  if (lastMessage.getType() !== MessageTypes.TOOL) return false;
@@ -19,10 +19,12 @@ describe('BrowserTools', () => {
19
19
  expect(EBrowserTools.BACK).toBe('browser_back');
20
20
  expect(EBrowserTools.SCREENSHOT).toBe('browser_screenshot');
21
21
  expect(EBrowserTools.GET_PAGE_STATE).toBe('browser_get_page_state');
22
+ expect(EBrowserTools.KEYPRESS).toBe('browser_keypress');
23
+ expect(EBrowserTools.SWITCH_TAB).toBe('browser_switch_tab');
22
24
  });
23
25
 
24
- it('should have exactly 10 browser tools', () => {
25
- expect(Object.keys(EBrowserTools).length).toBe(10);
26
+ it('should have exactly 12 browser tools', () => {
27
+ expect(Object.keys(EBrowserTools).length).toBe(12);
26
28
  });
27
29
  });
28
30
 
@@ -82,8 +84,8 @@ describe('BrowserTools', () => {
82
84
  tools = createBrowserTools();
83
85
  });
84
86
 
85
- it('should create exactly 10 browser tools', () => {
86
- expect(tools.length).toBe(10);
87
+ it('should create exactly 12 browser tools', () => {
88
+ expect(tools.length).toBe(12);
87
89
  });
88
90
 
89
91
  it('should create tools with correct names', () => {