@aj-archipelago/cortex 1.4.32 → 1.4.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config.js CHANGED
@@ -575,6 +575,72 @@ var config = convict({
575
575
  "maxImageSize": 5242880,
576
576
  "supportsStreaming": true
577
577
  },
578
+ "claude-sonnet-4": {
579
+ "type": "CLAUDE-ANTHROPIC",
580
+ "emulateOpenAIChatModel": "claude-sonnet-4",
581
+ "endpoints": [
582
+ {
583
+ "name": "Anthropic Claude Sonnet 4",
584
+ "url": "https://api.anthropic.com/v1/messages",
585
+ "headers": {
586
+ "x-api-key": "{{CLAUDE_API_KEY}}",
587
+ "Content-Type": "application/json"
588
+ },
589
+ "params": {
590
+ "model": "claude-sonnet-4-20250514"
591
+ },
592
+ "requestsPerSecond": 10
593
+ }
594
+ ],
595
+ "maxTokenLength": 200000,
596
+ "maxReturnTokens": 64000,
597
+ "maxImageSize": 31457280,
598
+ "supportsStreaming": true
599
+ },
600
+ "claude-45-sonnet": {
601
+ "type": "CLAUDE-ANTHROPIC",
602
+ "emulateOpenAIChatModel": "claude-4.5-sonnet",
603
+ "endpoints": [
604
+ {
605
+ "name": "Anthropic Claude 4.5 Sonnet",
606
+ "url": "https://api.anthropic.com/v1/messages",
607
+ "headers": {
608
+ "x-api-key": "{{CLAUDE_API_KEY}}",
609
+ "Content-Type": "application/json"
610
+ },
611
+ "params": {
612
+ "model": "claude-sonnet-4-5-20250514"
613
+ },
614
+ "requestsPerSecond": 10
615
+ }
616
+ ],
617
+ "maxTokenLength": 200000,
618
+ "maxReturnTokens": 64000,
619
+ "maxImageSize": 31457280,
620
+ "supportsStreaming": true
621
+ },
622
+ "claude-45-opus": {
623
+ "type": "CLAUDE-ANTHROPIC",
624
+ "emulateOpenAIChatModel": "claude-4.5-opus",
625
+ "endpoints": [
626
+ {
627
+ "name": "Anthropic Claude 4.5 Opus",
628
+ "url": "https://api.anthropic.com/v1/messages",
629
+ "headers": {
630
+ "x-api-key": "{{CLAUDE_API_KEY}}",
631
+ "Content-Type": "application/json"
632
+ },
633
+ "params": {
634
+ "model": "claude-opus-4-5-20250514"
635
+ },
636
+ "requestsPerSecond": 10
637
+ }
638
+ ],
639
+ "maxTokenLength": 200000,
640
+ "maxReturnTokens": 32000,
641
+ "maxImageSize": 31457280,
642
+ "supportsStreaming": true
643
+ },
578
644
  "gemini-flash-25-vision": {
579
645
  "type": "GEMINI-1.5-VISION",
580
646
  "emulateOpenAIChatModel": "gemini-flash-25",
@@ -778,6 +844,12 @@ var config = convict({
778
844
  env: 'OPENAI_API_KEY',
779
845
  sensitive: true
780
846
  },
847
+ claudeApiKey: {
848
+ format: String,
849
+ default: null,
850
+ env: 'CLAUDE_API_KEY',
851
+ sensitive: true
852
+ },
781
853
  openaiApiUrl: {
782
854
  format: String,
783
855
  default: 'https://api.openai.com/v1/completions',
@@ -1,4 +1,4 @@
1
- FROM node:18-alpine
1
+ FROM node:22-alpine
2
2
 
3
3
  WORKDIR /usr/src/app
4
4
 
package/lib/fileUtils.js CHANGED
@@ -1561,11 +1561,27 @@ async function syncAndStripFilesFromChatHistory(chatHistory, agentContext, chatI
1561
1561
 
1562
1562
  // Build lookup map from contextId to contextKey for updates
1563
1563
  const contextKeyMap = new Map(agentContext.map(ctx => [ctx.contextId, ctx.contextKey || null]));
1564
-
1564
+
1565
+ // Helper to normalize URLs by stripping query parameters (SAS tokens, etc.)
1566
+ // This allows matching URLs that have different query params but same base path
1567
+ const normalizeUrl = (url) => {
1568
+ if (!url) return null;
1569
+ try {
1570
+ // Handle GCS URLs (gs://) by keeping them as-is (no query params)
1571
+ if (url.startsWith('gs://')) return url;
1572
+ const parsed = new URL(url);
1573
+ return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
1574
+ } catch {
1575
+ // If URL parsing fails, return as-is
1576
+ return url;
1577
+ }
1578
+ };
1579
+
1565
1580
  // Build lookup maps for fast matching and context lookup (use ALL files, not just filtered)
1566
1581
  // This allows us to find files that exist in Redis but don't have inCollection set yet
1582
+ // URLs are normalized (query params stripped) to handle SAS tokens and other transient params
1567
1583
  const collectionByHash = new Map(allFiles.filter(f => f.hash).map(f => [f.hash, f]));
1568
- const collectionByUrl = new Map(allFiles.filter(f => f.url).map(f => [f.url, f]));
1584
+ const collectionByUrl = new Map(allFiles.filter(f => f.url).map(f => [normalizeUrl(f.url), f]));
1569
1585
  const collectionByGcs = new Map(allFiles.filter(f => f.gcs).map(f => [f.gcs, f]));
1570
1586
 
1571
1587
  // Helper to get file from collection (by hash, URL, or GCS) to find _contextId
@@ -1573,12 +1589,15 @@ async function syncAndStripFilesFromChatHistory(chatHistory, agentContext, chatI
1573
1589
  const fileHash = contentObj.hash;
1574
1590
  const fileUrl = contentObj.url || contentObj.image_url?.url;
1575
1591
  const fileGcs = contentObj.gcs;
1576
-
1592
+
1577
1593
  if (fileHash && collectionByHash.has(fileHash)) {
1578
1594
  return collectionByHash.get(fileHash);
1579
1595
  }
1580
- if (fileUrl && collectionByUrl.has(fileUrl)) {
1581
- return collectionByUrl.get(fileUrl);
1596
+ if (fileUrl) {
1597
+ const normalizedUrl = normalizeUrl(fileUrl);
1598
+ if (normalizedUrl && collectionByUrl.has(normalizedUrl)) {
1599
+ return collectionByUrl.get(normalizedUrl);
1600
+ }
1582
1601
  }
1583
1602
  if (fileGcs && collectionByGcs.has(fileGcs)) {
1584
1603
  return collectionByGcs.get(fileGcs);
@@ -31,7 +31,7 @@ class LocalStorage extends StorageStrategy {
31
31
  const data = await fs.promises.readFile(this.filePath, 'utf8');
32
32
  return JSON.parse(data);
33
33
  } catch (error) {
34
- logger.error(`Error loading pathways from ${this.filePath}:`, error);
34
+ logger.error(`Error loading pathways from ${this.filePath}: ${error.message}`);
35
35
  throw error;
36
36
  }
37
37
  }
@@ -88,7 +88,7 @@ class AzureBlobStorage extends StorageStrategy {
88
88
  logger.info(`Loaded pathways from Azure Blob Storage. ${Object.keys(parsedData).map(user => `${user}(${Object.keys(parsedData[user])})`).join(', ')}`);
89
89
  return parsedData;
90
90
  } catch (error) {
91
- logger.error('Error loading pathways from Azure Blob Storage:', error);
91
+ logger.error(`Error loading pathways from Azure Blob Storage: ${error.message}`);
92
92
  throw error;
93
93
  }
94
94
  }
@@ -99,7 +99,7 @@ class AzureBlobStorage extends StorageStrategy {
99
99
  const content = JSON.stringify(data, null, 2);
100
100
  await blockBlobClient.upload(content, content.length);
101
101
  } catch (error) {
102
- logger.error('Error saving pathways to Azure Blob Storage:', error);
102
+ logger.error(`Error saving pathways to Azure Blob Storage: ${error.message}`);
103
103
  }
104
104
  }
105
105
 
@@ -155,7 +155,7 @@ class S3Storage extends StorageStrategy {
155
155
  const dataString = await streamToString(readableStream);
156
156
  return JSON.parse(dataString);
157
157
  } catch (error) {
158
- logger.error('Error loading pathways from S3:', error);
158
+ logger.error(`Error loading pathways from S3: ${error.message}`);
159
159
  throw error;
160
160
  }
161
161
  }
@@ -170,7 +170,7 @@ class S3Storage extends StorageStrategy {
170
170
  };
171
171
  await this.s3.putObject(params);
172
172
  } catch (error) {
173
- logger.error('Error saving pathways to S3:', error);
173
+ logger.error(`Error saving pathways to S3: ${error.message}`);
174
174
  }
175
175
  }
176
176
 
@@ -544,7 +544,7 @@ class PathwayManager {
544
544
 
545
545
  return this.pathways;
546
546
  } catch (error) {
547
- logger.error('Error in getLatestPathways:', error);
547
+ logger.error(`Error in getLatestPathways: ${error.message}`);
548
548
  throw error;
549
549
  }
550
550
  }
@@ -427,4 +427,24 @@ const sendToolFinish = async (requestId, toolCallId, success, error = null) => {
427
427
  }
428
428
  };
429
429
 
430
- export { callPathway, gpt3Encode, gpt3Decode, say, callTool, addCitationsToResolver, sendToolStart, sendToolFinish };
430
+ /**
431
+ * Wrap a promise with a timeout
432
+ * @param {Promise} promise - The promise to wrap
433
+ * @param {number} timeoutMs - Timeout in milliseconds
434
+ * @param {string} errorMessage - Error message if timeout occurs
435
+ * @returns {Promise} - The original promise or rejection on timeout
436
+ */
437
+ const withTimeout = (promise, timeoutMs, errorMessage = 'Operation timed out') => {
438
+ let timeoutId;
439
+ const timeoutPromise = new Promise((_, reject) => {
440
+ timeoutId = setTimeout(() => {
441
+ reject(new Error(errorMessage));
442
+ }, timeoutMs);
443
+ });
444
+
445
+ return Promise.race([promise, timeoutPromise]).finally(() => {
446
+ clearTimeout(timeoutId);
447
+ });
448
+ };
449
+
450
+ export { callPathway, gpt3Encode, gpt3Decode, say, callTool, addCitationsToResolver, sendToolStart, sendToolFinish, withTimeout };
@@ -101,7 +101,16 @@ const createLimiter = (endpoint, name, index) => {
101
101
  logger.debug(`Limiter request cancelled for ${cortexId}-${name}-${index}: Id: ${info.options.id || 'none'}`);
102
102
  endpoint.monitor.incrementErrorCount();
103
103
  } else {
104
- logger.error(`Limiter request failed for ${cortexId}-${name}-${index}: Id: ${info.options.id || 'none'}: ${error?.message || error}`);
104
+ const errorMsg = error?.message || error;
105
+ const status = error?.status || 'unknown';
106
+ logger.error(`Limiter request failed for ${cortexId}-${name}-${index}: Id: ${info.options.id || 'none'}: [${status}] ${errorMsg}`);
107
+ // Log response data if available (helpful for debugging 400 errors)
108
+ if (error?.responseData) {
109
+ const responseDataStr = typeof error.responseData === 'string'
110
+ ? error.responseData.substring(0, 1000)
111
+ : JSON.stringify(error.responseData, null, 2).substring(0, 1000);
112
+ logger.error(`Response data: ${responseDataStr}`);
113
+ }
105
114
  }
106
115
  });
107
116
 
@@ -239,11 +248,46 @@ const requestWithMonitor = async (endpoint, url, data, axiosConfigObj) => {
239
248
  } catch (error) {
240
249
  // throw new error with duration as part of the error data
241
250
  const { code, name } = error;
242
- const finalStatus = error?.response?.status ?? error?.status
243
- const statusText = error?.response?.statusText ?? error?.statusText
244
- const errorMessage = error?.response?.data?.message ?? error?.response?.data?.error?.message ?? error?.message ?? String(error);
251
+ const finalStatus = error?.response?.status ?? error?.status;
252
+ const statusText = error?.response?.statusText ?? error?.statusText;
253
+ let responseData = error?.response?.data;
254
+
255
+ // For streaming requests with errors, the response data is a stream that needs to be consumed
256
+ if (responseData && typeof responseData.on === 'function') {
257
+ try {
258
+ const chunks = [];
259
+ for await (const chunk of responseData) {
260
+ chunks.push(chunk);
261
+ }
262
+ const streamContent = Buffer.concat(chunks).toString('utf-8');
263
+ try {
264
+ responseData = JSON.parse(streamContent);
265
+ } catch {
266
+ responseData = { rawContent: streamContent.substring(0, 2000) };
267
+ }
268
+ } catch (streamError) {
269
+ logger.debug(`Could not read error stream: ${streamError.message}`);
270
+ responseData = null;
271
+ }
272
+ }
273
+
274
+ // Extract error message from various possible locations in the response
275
+ const errorMessage = responseData?.message
276
+ ?? responseData?.error?.message
277
+ ?? responseData?.error?.status
278
+ ?? responseData?.rawContent?.substring(0, 500)
279
+ ?? error?.message
280
+ ?? String(error);
281
+
282
+ // Log full response data for debugging 4xx errors (especially 400)
283
+ if (finalStatus >= 400 && finalStatus < 500 && responseData) {
284
+ const responseDataStr = typeof responseData === 'string'
285
+ ? responseData.substring(0, 2000)
286
+ : JSON.stringify(responseData, null, 2).substring(0, 2000);
287
+ logger.error(`HTTP ${finalStatus} error response data: ${responseDataStr}`);
288
+ }
245
289
 
246
- throw { code, message: errorMessage, status: finalStatus, statusText, name, duration: endpoint?.monitor?.incrementErrorCount(callId, finalStatus) };
290
+ throw { code, message: errorMessage, status: finalStatus, statusText, name, responseData, duration: endpoint?.monitor?.incrementErrorCount(callId, finalStatus) };
247
291
  }
248
292
  let duration;
249
293
  if (response.status >= 200 && response.status < 300) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.4.32",
3
+ "version": "1.4.33",
4
4
  "description": "Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.",
5
5
  "private": false,
6
6
  "repository": {
@@ -0,0 +1,82 @@
1
+ // sys_compress_context.js
2
+ // Compresses chat history containing tool calls and results while preserving critical information
3
+ // Used when context window is approaching limits to prevent 400 errors
4
+
5
+ import { Prompt } from '../../../server/prompt.js';
6
+ import logger from '../../../lib/logger.js';
7
+
8
+ export default {
9
+ prompt: [
10
+ new Prompt({ messages: [
11
+ {
12
+ "role": "system",
13
+ "content": `You are an AI assistant that compresses conversation history while preserving critical information needed to continue the task.
14
+
15
+ Your job is to create a concise summary from tool calls and their results that:
16
+
17
+ 1. **Preserves the research intent** - What was the user trying to find or accomplish?
18
+
19
+ 2. **Summarizes tool calls and results** - For each tool call, include:
20
+ - The tool name and purpose
21
+ - Key results and findings (especially data, facts, file names, URLs)
22
+ - Important decisions made based on results
23
+
24
+ 3. **CRITICAL: Preserve exact data** - You MUST preserve:
25
+ - Exact numbers, percentages, dollar amounts, dates, statistics
26
+ - ALL URLs exactly as written (never truncate URLs)
27
+ - Source citations (publication names, report numbers, author names)
28
+ - File names and paths exactly as they appear
29
+
30
+ 4. **Maintain citation integrity** - Preserve file names, URLs, source references
31
+
32
+ 5. **Keep it actionable** - What has been accomplished? What still needs to be done?
33
+
34
+ Format as a clear narrative that another AI agent could read to understand the research progress.
35
+
36
+ Be concise but comprehensive. When in doubt, preserve more detail for numbers, URLs, and citations.`
37
+ },
38
+ {
39
+ "role": "user",
40
+ "content": `Please compress the following tool calls and results into a concise research summary:
41
+
42
+ {{{researchContent}}}
43
+
44
+ Provide a clear summary preserving all URLs, citations, and numerical data.`
45
+ }
46
+ ]})
47
+ ],
48
+ inputParameters: {
49
+ researchContent: '',
50
+ language: "English",
51
+ },
52
+ model: 'gemini-flash-3-vision',
53
+ useInputChunking: false,
54
+ enableDuplicateRequests: false,
55
+ timeout: 120,
56
+
57
+ executePathway: async ({args, runAllPrompts}) => {
58
+ try {
59
+ // Extract URLs for validation
60
+ const urls = new Set();
61
+ const content = args.researchContent || '';
62
+ const urlMatches = content.match(/https?:\/\/[^\s\)\]"']+/g);
63
+ if (urlMatches) urlMatches.forEach(url => urls.add(url));
64
+
65
+ const result = await runAllPrompts(args);
66
+
67
+ // Validate URL preservation
68
+ if (urls.size > 0 && typeof result === 'string') {
69
+ const preserved = Array.from(urls).filter(url => result.includes(url));
70
+ const rate = preserved.length / urls.size;
71
+ if (rate < 0.7) {
72
+ logger.warn(`Context compression preserved only ${(rate * 100).toFixed(0)}% of URLs (${preserved.length}/${urls.size})`);
73
+ }
74
+ }
75
+
76
+ return result;
77
+ } catch (error) {
78
+ logger.error(`Error in sys_compress_context: ${error.message}`);
79
+ return `[Compression failed] Previous tool calls have been summarized to save context space.`;
80
+ }
81
+ }
82
+ };
@@ -1,8 +1,11 @@
1
1
  // sys_entity_agent.js
2
2
  // Agentic extension of the entity system that uses OpenAI's tool calling API
3
3
  const MAX_TOOL_CALLS = 50;
4
+ const TOOL_TIMEOUT_MS = 120000; // 2 minute timeout per tool call
5
+ const MAX_TOOL_RESULT_LENGTH = 50000; // Truncate oversized tool results to prevent context overflow
4
6
 
5
- import { callPathway, callTool, say, sendToolStart, sendToolFinish } from '../../../lib/pathwayTools.js';
7
+ import { callPathway, callTool, say, sendToolStart, sendToolFinish, withTimeout } from '../../../lib/pathwayTools.js';
8
+ import { publishRequestProgress } from '../../../lib/redisSubscription.js';
6
9
  import logger from '../../../lib/logger.js';
7
10
  import { config } from '../../../config.js';
8
11
  import { syncAndStripFilesFromChatHistory } from '../../../lib/fileUtils.js';
@@ -138,10 +141,13 @@ export default {
138
141
  // Create an isolated copy of messages for this tool
139
142
  const toolMessages = JSON.parse(JSON.stringify(preToolCallMessages));
140
143
 
141
- // Get the tool definition to check for icon
144
+ // Get the tool definition to check for icon and timeout
142
145
  const toolDefinition = entityTools[toolFunction]?.definition;
143
146
  const toolIcon = toolDefinition?.icon || '🛠️';
144
147
 
148
+ // Get timeout from tool definition or use default
149
+ const toolTimeout = toolDefinition?.timeout || TOOL_TIMEOUT_MS;
150
+
145
151
  // Get the user message for the tool
146
152
  const toolUserMessage = toolArgs.userMessage || `Executing tool: ${toolCall.function.name}`;
147
153
 
@@ -155,13 +161,19 @@ export default {
155
161
  // Continue execution even if start message fails
156
162
  }
157
163
 
158
- const toolResult = await callTool(toolFunction, {
159
- ...args,
160
- ...toolArgs,
161
- toolFunction,
162
- chatHistory: toolMessages,
163
- stream: false
164
- }, entityTools, pathwayResolver);
164
+ // Wrap tool call with timeout to prevent hanging
165
+ const toolResult = await withTimeout(
166
+ callTool(toolFunction, {
167
+ ...args,
168
+ ...toolArgs,
169
+ toolFunction,
170
+ chatHistory: toolMessages,
171
+ stream: false,
172
+ useMemory: false // Disable memory synthesis for tool calls
173
+ }, entityTools, pathwayResolver),
174
+ toolTimeout,
175
+ `Tool ${toolCall.function.name} timed out after ${toolTimeout / 1000}s`
176
+ );
165
177
 
166
178
  // Tool calls and results need to be paired together in the message history
167
179
  // Add the tool call to the isolated message history
@@ -311,7 +323,9 @@ export default {
311
323
  messages: toolMessages
312
324
  };
313
325
  } catch (error) {
314
- logger.error(`Error executing tool ${toolCall?.function?.name || 'unknown'}: ${error.message}`);
326
+ // Detect if this is a timeout error for clearer logging
327
+ const isTimeout = error.message?.includes('timed out');
328
+ logger.error(`${isTimeout ? 'Timeout' : 'Error'} executing tool ${toolCall?.function?.name || 'unknown'}: ${error.message}`);
315
329
 
316
330
  // Send tool finish message (error)
317
331
  // Get requestId and toolCallId if not already defined (in case error occurred before they were set)
@@ -412,7 +426,17 @@ export default {
412
426
  );
413
427
  }
414
428
 
415
- args.chatHistory = finalMessages;
429
+ // Truncate oversized tool results to prevent context overflow
430
+ args.chatHistory = finalMessages.map(msg => {
431
+ if (msg.role === 'tool' && msg.content && msg.content.length > MAX_TOOL_RESULT_LENGTH) {
432
+ logger.warn(`Truncating oversized tool result (${msg.content.length} chars) for ${msg.name || 'unknown tool'}`);
433
+ return {
434
+ ...msg,
435
+ content: msg.content.substring(0, MAX_TOOL_RESULT_LENGTH) + '\n\n[Content truncated due to length]'
436
+ };
437
+ }
438
+ return msg;
439
+ });
416
440
 
417
441
  // clear any accumulated pathwayResolver errors from the tools
418
442
  pathwayResolver.errors = [];
@@ -429,16 +453,27 @@ export default {
429
453
 
430
454
  // Check if promptAndParse returned null (model call failed)
431
455
  if (!result) {
432
- const errorMessage = pathwayResolver.errors.length > 0
456
+ const errorMessage = pathwayResolver.errors.length > 0
433
457
  ? pathwayResolver.errors.join(', ')
434
458
  : 'Model request failed - no response received';
435
459
  logger.error(`promptAndParse returned null during tool callback: ${errorMessage}`);
436
460
  const errorResponse = await generateErrorResponse(new Error(errorMessage), args, pathwayResolver);
437
461
  // Ensure errors are cleared before returning
438
462
  pathwayResolver.errors = [];
463
+
464
+ // In streaming mode, the toolCallback is invoked fire-and-forget by the plugin,
465
+ // so we must stream the error response directly to the client and close the stream
466
+ const requestId = pathwayResolver.rootRequestId || pathwayResolver.requestId;
467
+ publishRequestProgress({
468
+ requestId,
469
+ progress: 1,
470
+ data: JSON.stringify(errorResponse),
471
+ info: JSON.stringify(pathwayResolver.pathwayResultData || {}),
472
+ error: ''
473
+ });
439
474
  return errorResponse;
440
475
  }
441
-
476
+
442
477
  return result;
443
478
  } catch (parseError) {
444
479
  // If promptAndParse fails, generate error response instead of re-throwing
@@ -446,6 +481,17 @@ export default {
446
481
  const errorResponse = await generateErrorResponse(parseError, args, pathwayResolver);
447
482
  // Ensure errors are cleared before returning
448
483
  pathwayResolver.errors = [];
484
+
485
+ // In streaming mode, the toolCallback is invoked fire-and-forget by the plugin,
486
+ // so we must stream the error response directly to the client and close the stream
487
+ const requestId = pathwayResolver.rootRequestId || pathwayResolver.requestId;
488
+ publishRequestProgress({
489
+ requestId,
490
+ progress: 1,
491
+ data: JSON.stringify(errorResponse),
492
+ info: JSON.stringify(pathwayResolver.pathwayResultData || {}),
493
+ error: ''
494
+ });
449
495
  return errorResponse;
450
496
  }
451
497
  }
@@ -600,10 +646,10 @@ export default {
600
646
 
601
647
  // truncate the chat history in case there is really long content
602
648
  const truncatedChatHistory = resolver.modelExecutor.plugin.truncateMessagesToTargetLength(args.chatHistory, null, 1000);
603
-
649
+
604
650
  // Asynchronously manage memory for this context
605
651
  if (args.aiMemorySelfModify && args.useMemory) {
606
- callPathway('sys_memory_manager', { ...args, chatHistory: truncatedChatHistory, stream: false })
652
+ callPathway('sys_memory_manager', { ...args, chatHistory: truncatedChatHistory, stream: false })
607
653
  .catch(error => logger.error(error?.message || "Error in sys_memory_manager pathway"));
608
654
  }
609
655
 
@@ -632,6 +678,10 @@ export default {
632
678
  memoryLookupRequired = false;
633
679
  }
634
680
 
681
+ // Update pathwayResolver.args with stripped chatHistory
682
+ // This ensures toolCallback receives the processed history, not the original
683
+ pathwayResolver.args = {...args};
684
+
635
685
  try {
636
686
  let currentMessages = JSON.parse(JSON.stringify(args.chatHistory));
637
687
 
@@ -225,7 +225,7 @@ REMEMBER:
225
225
  .sort((a, b) => a.index - b.index)
226
226
  .map(item => item.result);
227
227
  } catch (error) {
228
- logger.error('Error processing chunks:', error);
228
+ logger.error(`Error processing chunks: ${error.message}`);
229
229
  throw error;
230
230
  }
231
231
  };
@@ -26,6 +26,7 @@ import Gemini3ImagePlugin from './plugins/gemini3ImagePlugin.js';
26
26
  import Gemini3ReasoningVisionPlugin from './plugins/gemini3ReasoningVisionPlugin.js';
27
27
  import Claude3VertexPlugin from './plugins/claude3VertexPlugin.js';
28
28
  import Claude4VertexPlugin from './plugins/claude4VertexPlugin.js';
29
+ import ClaudeAnthropicPlugin from './plugins/claudeAnthropicPlugin.js';
29
30
  import NeuralSpacePlugin from './plugins/neuralSpacePlugin.js';
30
31
  import RunwareAiPlugin from './plugins/runwareAiPlugin.js';
31
32
  import ReplicateApiPlugin from './plugins/replicateApiPlugin.js';
@@ -122,6 +123,9 @@ class ModelExecutor {
122
123
  case 'CLAUDE-4-VERTEX':
123
124
  plugin = new Claude4VertexPlugin(pathway, model);
124
125
  break;
126
+ case 'CLAUDE-ANTHROPIC':
127
+ plugin = new ClaudeAnthropicPlugin(pathway, model);
128
+ break;
125
129
  case 'RUNWARE-AI':
126
130
  plugin = new RunwareAiPlugin(pathway, model);
127
131
  break;