@aj-archipelago/cortex 1.4.31 → 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 +72 -0
- package/helper-apps/cortex-file-handler/Dockerfile +1 -1
- package/lib/fileUtils.js +24 -5
- package/lib/pathwayManager.js +13 -6
- package/lib/pathwayTools.js +92 -1
- package/lib/requestExecutor.js +49 -5
- package/package.json +1 -1
- package/pathways/system/entity/sys_compress_context.js +82 -0
- package/pathways/system/entity/sys_entity_agent.js +106 -18
- package/pathways/transcribe_gemini.js +1 -1
- package/server/clientToolCallbacks.js +241 -0
- package/server/executeWorkspace.js +7 -0
- package/server/graphql.js +3 -1
- package/server/modelExecutor.js +4 -0
- package/server/pathwayResolver.js +102 -12
- package/server/plugins/claudeAnthropicPlugin.js +84 -0
- package/server/plugins/gemini15ChatPlugin.js +17 -0
- package/server/plugins/gemini15VisionPlugin.js +67 -8
- package/server/plugins/grokResponsesPlugin.js +2 -0
- package/server/plugins/openAiVisionPlugin.js +4 -2
- package/server/resolver.js +37 -2
- package/test.log +42834 -0
- package/tests/integration/clientToolCallbacks.test.js +161 -0
- package/tests/integration/features/tools/fileCollection.test.js +1 -1
- package/tests/integration/rest/vendors/claude_anthropic_direct.test.js +197 -0
- package/tests/unit/plugins/claudeAnthropicPlugin.test.js +236 -0
- package/tests/unit/plugins/multimodal_conversion.test.js +16 -6
- package/tests/unit/sys_entity_agent_errors.test.js +792 -0
- package/RELEASE_NOTES_20251231_103631.md +0 -15
- package/RELEASE_NOTES_20251231_110946.md +0 -5
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',
|
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
|
|
1581
|
-
|
|
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);
|
package/lib/pathwayManager.js
CHANGED
|
@@ -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}
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
173
|
+
logger.error(`Error saving pathways to S3: ${error.message}`);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -349,6 +349,7 @@ class PathwayManager {
|
|
|
349
349
|
const promptName = typeof promptItem === 'string' ? defaultName : (promptItem.name || defaultName);
|
|
350
350
|
const promptFiles = typeof promptItem === 'string' ? [] : (promptItem.files || []);
|
|
351
351
|
const cortexPathwayName = typeof promptItem === 'string' ? null : (promptItem.cortexPathwayName || null);
|
|
352
|
+
const researchMode = typeof promptItem === 'string' ? undefined : (promptItem.researchMode !== undefined ? promptItem.researchMode : undefined);
|
|
352
353
|
|
|
353
354
|
const messages = [];
|
|
354
355
|
|
|
@@ -383,6 +384,11 @@ class PathwayManager {
|
|
|
383
384
|
prompt.cortexPathwayName = cortexPathwayName;
|
|
384
385
|
}
|
|
385
386
|
|
|
387
|
+
// Preserve researchMode if present
|
|
388
|
+
if (researchMode !== undefined) {
|
|
389
|
+
prompt.researchMode = researchMode;
|
|
390
|
+
}
|
|
391
|
+
|
|
386
392
|
return prompt;
|
|
387
393
|
}
|
|
388
394
|
|
|
@@ -460,6 +466,7 @@ class PathwayManager {
|
|
|
460
466
|
prompt: String!
|
|
461
467
|
files: [String!]
|
|
462
468
|
cortexPathwayName: String
|
|
469
|
+
researchMode: Boolean
|
|
463
470
|
}
|
|
464
471
|
|
|
465
472
|
input PathwayInput {
|
|
@@ -537,7 +544,7 @@ class PathwayManager {
|
|
|
537
544
|
|
|
538
545
|
return this.pathways;
|
|
539
546
|
} catch (error) {
|
|
540
|
-
logger.error(
|
|
547
|
+
logger.error(`Error in getLatestPathways: ${error.message}`);
|
|
541
548
|
throw error;
|
|
542
549
|
}
|
|
543
550
|
}
|
package/lib/pathwayTools.js
CHANGED
|
@@ -6,6 +6,7 @@ import { getSemanticChunks } from "../server/chunker.js";
|
|
|
6
6
|
import logger from '../lib/logger.js';
|
|
7
7
|
import { requestState } from '../server/requestState.js';
|
|
8
8
|
import { processPathwayParameters } from '../server/typeDef.js';
|
|
9
|
+
import { waitForClientToolResult } from '../server/clientToolCallbacks.js';
|
|
9
10
|
|
|
10
11
|
// callPathway - call a pathway from another pathway
|
|
11
12
|
const callPathway = async (pathwayName, inArgs, pathwayResolver) => {
|
|
@@ -91,6 +92,76 @@ const callTool = async (toolName, args, toolDefinitions, pathwayResolver) => {
|
|
|
91
92
|
logger.debug(`callTool: Starting execution of ${toolName} ${JSON.stringify(logArgs)}`);
|
|
92
93
|
|
|
93
94
|
try {
|
|
95
|
+
// Check if this is a client-side tool
|
|
96
|
+
if (toolDef.clientSide === true || toolDef.definition?.clientSide === true) {
|
|
97
|
+
logger.info(`Tool ${toolName} is a client-side tool - waiting for client execution`);
|
|
98
|
+
|
|
99
|
+
const toolCallbackId = `${toolName}_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
100
|
+
|
|
101
|
+
// Explicitly publish the marker to the stream so the client receives it
|
|
102
|
+
if (pathwayResolver) {
|
|
103
|
+
const requestId = pathwayResolver.rootRequestId || pathwayResolver.requestId;
|
|
104
|
+
|
|
105
|
+
const toolCallbackData = {
|
|
106
|
+
toolUsed: [toolName],
|
|
107
|
+
clientSideTool: true,
|
|
108
|
+
toolCallbackName: toolName,
|
|
109
|
+
toolCallbackId: toolCallbackId,
|
|
110
|
+
toolCallbackMessage: args.userMessage || `Executing ${toolName}...`,
|
|
111
|
+
chatId: args.chatId || "",
|
|
112
|
+
requestId: requestId, // Include requestId so client can submit tool results
|
|
113
|
+
toolArgs: args
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
logger.info(`Publishing client-side tool marker to requestId: ${requestId}, toolCallbackId: ${toolCallbackId}`);
|
|
118
|
+
await publishRequestProgress({
|
|
119
|
+
requestId,
|
|
120
|
+
progress: 0.5,
|
|
121
|
+
data: JSON.stringify(""),
|
|
122
|
+
info: JSON.stringify(toolCallbackData)
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
logger.error(`Error publishing client-side tool marker: ${error.message}`);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Wait for the client to execute the tool and send back the result
|
|
130
|
+
logger.info(`Waiting for client tool result: ${toolCallbackId}`);
|
|
131
|
+
try {
|
|
132
|
+
// Use 5 minute timeout to accommodate longer operations like CreateApplet
|
|
133
|
+
const clientResult = await waitForClientToolResult(toolCallbackId, requestId, 300000);
|
|
134
|
+
logger.info(`Received client tool result for ${toolCallbackId}: ${JSON.stringify(clientResult).substring(0, 200)}`);
|
|
135
|
+
|
|
136
|
+
// If the client reported an error, throw it
|
|
137
|
+
if (!clientResult.success) {
|
|
138
|
+
throw new Error(clientResult.error || 'Client tool execution failed');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Return the client's result
|
|
142
|
+
toolResult = typeof clientResult.data === 'string'
|
|
143
|
+
? clientResult.data
|
|
144
|
+
: JSON.stringify(clientResult.data);
|
|
145
|
+
|
|
146
|
+
// Update resolver with tool result
|
|
147
|
+
pathwayResolver.tool = JSON.stringify({
|
|
148
|
+
...toolCallbackData,
|
|
149
|
+
result: clientResult.data
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
result: toolResult,
|
|
154
|
+
images: []
|
|
155
|
+
};
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error(`Error waiting for client tool result: ${error.message}`);
|
|
158
|
+
throw new Error(`Client tool execution failed: ${error.message}`);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
throw new Error('PathwayResolver is required for client-side tools');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
94
165
|
const pathwayName = toolDef.pathwayName;
|
|
95
166
|
// Merge hard-coded pathway parameters with runtime args
|
|
96
167
|
const mergedArgs = {
|
|
@@ -356,4 +427,24 @@ const sendToolFinish = async (requestId, toolCallId, success, error = null) => {
|
|
|
356
427
|
}
|
|
357
428
|
};
|
|
358
429
|
|
|
359
|
-
|
|
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 };
|
package/lib/requestExecutor.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
+
};
|