@aj-archipelago/cortex 1.3.62 → 1.3.63
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/.github/workflows/cortex-file-handler-test.yml +61 -0
- package/README.md +31 -7
- package/config/default.example.json +15 -0
- package/config.js +133 -12
- package/helper-apps/cortex-autogen2/DigiCertGlobalRootCA.crt.pem +22 -0
- package/helper-apps/cortex-autogen2/Dockerfile +31 -0
- package/helper-apps/cortex-autogen2/Dockerfile.worker +41 -0
- package/helper-apps/cortex-autogen2/README.md +183 -0
- package/helper-apps/cortex-autogen2/__init__.py +1 -0
- package/helper-apps/cortex-autogen2/agents.py +131 -0
- package/helper-apps/cortex-autogen2/docker-compose.yml +20 -0
- package/helper-apps/cortex-autogen2/function_app.py +55 -0
- package/helper-apps/cortex-autogen2/host.json +15 -0
- package/helper-apps/cortex-autogen2/main.py +126 -0
- package/helper-apps/cortex-autogen2/poetry.lock +3652 -0
- package/helper-apps/cortex-autogen2/pyproject.toml +36 -0
- package/helper-apps/cortex-autogen2/requirements.txt +20 -0
- package/helper-apps/cortex-autogen2/send_task.py +105 -0
- package/helper-apps/cortex-autogen2/services/__init__.py +1 -0
- package/helper-apps/cortex-autogen2/services/azure_queue.py +85 -0
- package/helper-apps/cortex-autogen2/services/redis_publisher.py +153 -0
- package/helper-apps/cortex-autogen2/task_processor.py +488 -0
- package/helper-apps/cortex-autogen2/tools/__init__.py +24 -0
- package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +175 -0
- package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +601 -0
- package/helper-apps/cortex-autogen2/tools/coding_tools.py +72 -0
- package/helper-apps/cortex-autogen2/tools/download_tools.py +48 -0
- package/helper-apps/cortex-autogen2/tools/file_tools.py +545 -0
- package/helper-apps/cortex-autogen2/tools/search_tools.py +646 -0
- package/helper-apps/cortex-azure-cleaner/README.md +36 -0
- package/helper-apps/cortex-file-converter/README.md +93 -0
- package/helper-apps/cortex-file-converter/key_to_pdf.py +104 -0
- package/helper-apps/cortex-file-converter/list_blob_extensions.py +89 -0
- package/helper-apps/cortex-file-converter/process_azure_keynotes.py +181 -0
- package/helper-apps/cortex-file-converter/requirements.txt +1 -0
- package/helper-apps/cortex-file-handler/.env.test.azure.ci +7 -0
- package/helper-apps/cortex-file-handler/.env.test.azure.sample +1 -1
- package/helper-apps/cortex-file-handler/.env.test.gcs.ci +10 -0
- package/helper-apps/cortex-file-handler/.env.test.gcs.sample +2 -2
- package/helper-apps/cortex-file-handler/INTERFACE.md +41 -0
- package/helper-apps/cortex-file-handler/package.json +1 -1
- package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +41 -17
- package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +30 -15
- package/helper-apps/cortex-file-handler/scripts/test-azure.sh +32 -6
- package/helper-apps/cortex-file-handler/scripts/test-gcs.sh +24 -2
- package/helper-apps/cortex-file-handler/scripts/validate-env.js +128 -0
- package/helper-apps/cortex-file-handler/src/blobHandler.js +161 -51
- package/helper-apps/cortex-file-handler/src/constants.js +3 -0
- package/helper-apps/cortex-file-handler/src/fileChunker.js +10 -8
- package/helper-apps/cortex-file-handler/src/index.js +116 -9
- package/helper-apps/cortex-file-handler/src/redis.js +61 -1
- package/helper-apps/cortex-file-handler/src/services/ConversionService.js +11 -8
- package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +2 -2
- package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +88 -6
- package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +58 -0
- package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +25 -5
- package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +9 -0
- package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +120 -16
- package/helper-apps/cortex-file-handler/src/start.js +27 -17
- package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +52 -1
- package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +40 -0
- package/helper-apps/cortex-file-handler/tests/checkHashShortLived.test.js +553 -0
- package/helper-apps/cortex-file-handler/tests/cleanup.test.js +46 -52
- package/helper-apps/cortex-file-handler/tests/containerConversionFlow.test.js +451 -0
- package/helper-apps/cortex-file-handler/tests/containerNameParsing.test.js +229 -0
- package/helper-apps/cortex-file-handler/tests/containerParameterFlow.test.js +392 -0
- package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +7 -2
- package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +348 -0
- package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +23 -2
- package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +11 -5
- package/helper-apps/cortex-file-handler/tests/getOperations.test.js +58 -24
- package/helper-apps/cortex-file-handler/tests/postOperations.test.js +11 -4
- package/helper-apps/cortex-file-handler/tests/shortLivedUrlConversion.test.js +225 -0
- package/helper-apps/cortex-file-handler/tests/start.test.js +8 -12
- package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +80 -0
- package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +388 -22
- package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +74 -0
- package/lib/cortexResponse.js +153 -0
- package/lib/entityConstants.js +21 -3
- package/lib/logger.js +21 -4
- package/lib/pathwayTools.js +28 -9
- package/lib/util.js +49 -0
- package/package.json +1 -1
- package/pathways/basePathway.js +1 -0
- package/pathways/bing_afagent.js +54 -1
- package/pathways/call_tools.js +2 -3
- package/pathways/chat_jarvis.js +1 -1
- package/pathways/google_cse.js +27 -0
- package/pathways/grok_live_search.js +18 -0
- package/pathways/system/entity/memory/sys_memory_lookup_required.js +1 -0
- package/pathways/system/entity/memory/sys_memory_required.js +1 -0
- package/pathways/system/entity/memory/sys_search_memory.js +1 -0
- package/pathways/system/entity/sys_entity_agent.js +56 -4
- package/pathways/system/entity/sys_generator_quick.js +1 -0
- package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +26 -0
- package/pathways/system/entity/tools/sys_tool_google_search.js +141 -0
- package/pathways/system/entity/tools/sys_tool_grok_x_search.js +237 -0
- package/pathways/system/entity/tools/sys_tool_image.js +1 -1
- package/pathways/system/rest_streaming/sys_claude_37_sonnet.js +21 -0
- package/pathways/system/rest_streaming/sys_claude_41_opus.js +21 -0
- package/pathways/system/rest_streaming/sys_claude_4_sonnet.js +21 -0
- package/pathways/system/rest_streaming/sys_google_gemini_25_flash.js +25 -0
- package/pathways/system/rest_streaming/{sys_google_gemini_chat.js → sys_google_gemini_25_pro.js} +6 -4
- package/pathways/system/rest_streaming/sys_grok_4.js +23 -0
- package/pathways/system/rest_streaming/sys_grok_4_fast_non_reasoning.js +23 -0
- package/pathways/system/rest_streaming/sys_grok_4_fast_reasoning.js +23 -0
- package/pathways/system/rest_streaming/sys_openai_chat.js +3 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt41.js +22 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt41_mini.js +21 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt41_nano.js +21 -0
- package/pathways/system/rest_streaming/{sys_claude_35_sonnet.js → sys_openai_chat_gpt4_omni.js} +6 -4
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4_omni_mini.js +21 -0
- package/pathways/system/rest_streaming/{sys_claude_3_haiku.js → sys_openai_chat_gpt5.js} +7 -5
- package/pathways/system/rest_streaming/sys_openai_chat_gpt5_chat.js +21 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt5_mini.js +21 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt5_nano.js +21 -0
- package/pathways/system/rest_streaming/{sys_openai_chat_o1.js → sys_openai_chat_o3.js} +6 -3
- package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +3 -0
- package/pathways/system/workspaces/run_workspace_prompt.js +99 -0
- package/pathways/vision.js +1 -1
- package/server/graphql.js +1 -1
- package/server/modelExecutor.js +8 -0
- package/server/pathwayResolver.js +166 -16
- package/server/pathwayResponseParser.js +16 -8
- package/server/plugins/azureFoundryAgentsPlugin.js +1 -1
- package/server/plugins/claude3VertexPlugin.js +193 -45
- package/server/plugins/gemini15ChatPlugin.js +21 -0
- package/server/plugins/gemini15VisionPlugin.js +360 -0
- package/server/plugins/googleCsePlugin.js +94 -0
- package/server/plugins/grokVisionPlugin.js +365 -0
- package/server/plugins/modelPlugin.js +3 -1
- package/server/plugins/openAiChatPlugin.js +106 -13
- package/server/plugins/openAiVisionPlugin.js +42 -30
- package/server/resolver.js +28 -4
- package/server/rest.js +270 -53
- package/server/typeDef.js +1 -0
- package/tests/{mocks.js → helpers/mocks.js} +5 -2
- package/tests/{server.js → helpers/server.js} +2 -2
- package/tests/helpers/sseAssert.js +23 -0
- package/tests/helpers/sseClient.js +73 -0
- package/tests/helpers/subscriptionAssert.js +11 -0
- package/tests/helpers/subscriptions.js +113 -0
- package/tests/{sublong.srt → integration/features/translate/sublong.srt} +4543 -4543
- package/tests/integration/features/translate/translate_chunking_stream.test.js +100 -0
- package/tests/{translate_srt.test.js → integration/features/translate/translate_srt.test.js} +2 -2
- package/tests/integration/graphql/async/stream/agentic.test.js +477 -0
- package/tests/integration/graphql/async/stream/subscription_streaming.test.js +62 -0
- package/tests/integration/graphql/async/stream/sys_entity_start_streaming.test.js +71 -0
- package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +56 -0
- package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +66 -0
- package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +56 -0
- package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +72 -0
- package/tests/integration/graphql/features/google/sysToolGoogleSearch.test.js +96 -0
- package/tests/integration/graphql/features/grok/grok.test.js +688 -0
- package/tests/integration/graphql/features/grok/grok_x_search_tool.test.js +354 -0
- package/tests/{main.test.js → integration/graphql/features/main.test.js} +1 -1
- package/tests/{call_tools.test.js → integration/graphql/features/tools/call_tools.test.js} +2 -2
- package/tests/{vision.test.js → integration/graphql/features/vision/vision.test.js} +1 -1
- package/tests/integration/graphql/subscriptions/connection.test.js +26 -0
- package/tests/{openai_api.test.js → integration/rest/oai/openai_api.test.js} +63 -238
- package/tests/integration/rest/oai/tool_calling_api.test.js +343 -0
- package/tests/integration/rest/oai/tool_calling_streaming.test.js +85 -0
- package/tests/integration/rest/vendors/claude_streaming.test.js +47 -0
- package/tests/integration/rest/vendors/claude_tool_calling_streaming.test.js +75 -0
- package/tests/integration/rest/vendors/gemini_streaming.test.js +47 -0
- package/tests/integration/rest/vendors/gemini_tool_calling_streaming.test.js +75 -0
- package/tests/integration/rest/vendors/grok_streaming.test.js +55 -0
- package/tests/integration/rest/vendors/grok_tool_calling_streaming.test.js +75 -0
- package/tests/{azureAuthTokenHelper.test.js → unit/core/azureAuthTokenHelper.test.js} +1 -1
- package/tests/{chunkfunction.test.js → unit/core/chunkfunction.test.js} +2 -2
- package/tests/{config.test.js → unit/core/config.test.js} +3 -3
- package/tests/{encodeCache.test.js → unit/core/encodeCache.test.js} +1 -1
- package/tests/{fastLruCache.test.js → unit/core/fastLruCache.test.js} +1 -1
- package/tests/{handleBars.test.js → unit/core/handleBars.test.js} +1 -1
- package/tests/{memoryfunction.test.js → unit/core/memoryfunction.test.js} +2 -2
- package/tests/unit/core/mergeResolver.test.js +952 -0
- package/tests/{parser.test.js → unit/core/parser.test.js} +3 -3
- package/tests/unit/core/pathwayResolver.test.js +187 -0
- package/tests/{requestMonitor.test.js → unit/core/requestMonitor.test.js} +1 -1
- package/tests/{requestMonitorDurationEstimator.test.js → unit/core/requestMonitorDurationEstimator.test.js} +1 -1
- package/tests/{truncateMessages.test.js → unit/core/truncateMessages.test.js} +3 -3
- package/tests/{util.test.js → unit/core/util.test.js} +1 -1
- package/tests/{apptekTranslatePlugin.test.js → unit/plugins/apptekTranslatePlugin.test.js} +3 -3
- package/tests/{azureFoundryAgents.test.js → unit/plugins/azureFoundryAgents.test.js} +136 -1
- package/tests/{claude3VertexPlugin.test.js → unit/plugins/claude3VertexPlugin.test.js} +32 -10
- package/tests/{claude3VertexToolConversion.test.js → unit/plugins/claude3VertexToolConversion.test.js} +3 -3
- package/tests/unit/plugins/googleCsePlugin.test.js +111 -0
- package/tests/unit/plugins/grokVisionPlugin.test.js +1392 -0
- package/tests/{modelPlugin.test.js → unit/plugins/modelPlugin.test.js} +3 -3
- package/tests/{multimodal_conversion.test.js → unit/plugins/multimodal_conversion.test.js} +4 -4
- package/tests/{openAiChatPlugin.test.js → unit/plugins/openAiChatPlugin.test.js} +13 -4
- package/tests/{openAiToolPlugin.test.js → unit/plugins/openAiToolPlugin.test.js} +35 -27
- package/tests/{tokenHandlingTests.test.js → unit/plugins/tokenHandlingTests.test.js} +5 -5
- package/tests/{translate_apptek.test.js → unit/plugins/translate_apptek.test.js} +3 -3
- package/tests/{streaming.test.js → unit/plugins.streaming/plugin_stream_events.test.js} +19 -58
- package/helper-apps/mogrt-handler/tests/test-files/test.gif +0 -1
- package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +0 -1
- package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +0 -1
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4.js +0 -19
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4_32.js +0 -19
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4_turbo.js +0 -19
- package/pathways/system/workspaces/run_claude35_sonnet.js +0 -21
- package/pathways/system/workspaces/run_claude3_haiku.js +0 -20
- package/pathways/system/workspaces/run_gpt35turbo.js +0 -20
- package/pathways/system/workspaces/run_gpt4.js +0 -20
- package/pathways/system/workspaces/run_gpt4_32.js +0 -20
- package/tests/agentic.test.js +0 -256
- package/tests/pathwayResolver.test.js +0 -78
- package/tests/subscription.test.js +0 -387
- /package/tests/{subchunk.srt → integration/features/translate/subchunk.srt} +0 -0
- /package/tests/{subhorizontal.srt → integration/features/translate/subhorizontal.srt} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import serverFactory from '../../../../index.js';
|
|
3
|
+
import { createWsClient, ensureWsConnection, collectSubscriptionEvents, validateProgressMessage } from '../../../helpers/subscriptions.js';
|
|
4
|
+
|
|
5
|
+
let testServer;
|
|
6
|
+
let wsClient;
|
|
7
|
+
|
|
8
|
+
test.before(async () => {
|
|
9
|
+
process.env.CORTEX_ENABLE_REST = 'true';
|
|
10
|
+
const { server, startServer } = await serverFactory();
|
|
11
|
+
startServer && await startServer();
|
|
12
|
+
testServer = server;
|
|
13
|
+
|
|
14
|
+
wsClient = createWsClient();
|
|
15
|
+
await ensureWsConnection(wsClient);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test.after.always('cleanup', async () => {
|
|
19
|
+
if (wsClient) wsClient.dispose();
|
|
20
|
+
if (testServer) await testServer.stop();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test.serial('Translate pathway handles chunked async processing correctly', async (t) => {
|
|
24
|
+
const longText = `In the heart of the bustling metropolis, where skyscrapers pierce the clouds and streets pulse with endless energy,
|
|
25
|
+
a story unfolds. It's a tale of innovation and perseverance, of dreams taking flight in the digital age.
|
|
26
|
+
Entrepreneurs and visionaries gather in gleaming office towers, their minds focused on the next breakthrough that will reshape our world.
|
|
27
|
+
In labs and workshops, engineers and designers collaborate, their fingers dancing across keyboards as they write the future in lines of code.
|
|
28
|
+
The city never sleeps, its rhythm maintained by the constant flow of ideas and ambition. Coffee shops become impromptu meeting rooms,
|
|
29
|
+
where startups are born on napkins and partnerships forged over steaming lattes. The energy is palpable, electric, contagious.
|
|
30
|
+
In the background, servers hum in vast data centers, processing countless transactions and storing the collective knowledge of humanity.
|
|
31
|
+
The digital revolution continues unabated, transforming how we live, work, and connect with one another.
|
|
32
|
+
Young graduates fresh from university mingle with seasoned veterans, each bringing their unique perspective to the challenges at hand.
|
|
33
|
+
The boundaries between traditional industries blur as technology weaves its way into every aspect of business and society.
|
|
34
|
+
This is the story of progress, of human ingenuity pushing the boundaries of what's possible.
|
|
35
|
+
It's a narrative that continues to evolve, page by digital page, in the great book of human achievement.`.repeat(10);
|
|
36
|
+
|
|
37
|
+
const response = await testServer.executeOperation({
|
|
38
|
+
query: `
|
|
39
|
+
query TestQuery($text: String!, $to: String!) {
|
|
40
|
+
translate_gpt4_omni(text: $text, to: $to, async: true) {
|
|
41
|
+
result
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`,
|
|
45
|
+
variables: { text: longText, to: 'Spanish' }
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const requestId = response.body?.singleResult?.data?.translate_gpt4_omni?.result;
|
|
49
|
+
t.truthy(requestId);
|
|
50
|
+
|
|
51
|
+
const events = await collectSubscriptionEvents(wsClient, {
|
|
52
|
+
query: `
|
|
53
|
+
subscription OnRequestProgress($requestId: String!) {
|
|
54
|
+
requestProgress(requestIds: [$requestId]) {
|
|
55
|
+
requestId
|
|
56
|
+
progress
|
|
57
|
+
data
|
|
58
|
+
info
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`,
|
|
62
|
+
variables: { requestId },
|
|
63
|
+
}, 180000, { requireCompletion: true });
|
|
64
|
+
|
|
65
|
+
t.true(events.length > 0);
|
|
66
|
+
|
|
67
|
+
let lastProgress = -1;
|
|
68
|
+
let finalTranslation = null;
|
|
69
|
+
let progressValues = new Set();
|
|
70
|
+
let processingMessages = 0;
|
|
71
|
+
|
|
72
|
+
for (const event of events) {
|
|
73
|
+
const progress = event.data.requestProgress;
|
|
74
|
+
validateProgressMessage(t, progress, requestId);
|
|
75
|
+
|
|
76
|
+
if (progress.progress !== null) {
|
|
77
|
+
t.true(progress.progress >= lastProgress);
|
|
78
|
+
t.true(progress.progress >= 0 && progress.progress <= 1);
|
|
79
|
+
progressValues.add(progress.progress);
|
|
80
|
+
lastProgress = progress.progress;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (progress.progress === 1) {
|
|
84
|
+
t.truthy(progress.data);
|
|
85
|
+
const parsed = JSON.parse(progress.data);
|
|
86
|
+
t.true(typeof parsed === 'string');
|
|
87
|
+
t.true(parsed.length > 0);
|
|
88
|
+
finalTranslation = parsed;
|
|
89
|
+
} else {
|
|
90
|
+
processingMessages++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
t.true(progressValues.size >= 2);
|
|
95
|
+
t.true(processingMessages >= 1);
|
|
96
|
+
t.is(lastProgress, 1);
|
|
97
|
+
t.truthy(finalTranslation);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
|
package/tests/{translate_srt.test.js → integration/features/translate/translate_srt.test.js}
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import serverFactory from '
|
|
2
|
+
import serverFactory from '../../../../index.js';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname } from 'path';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { SubtitleUtils, parse } from '@aj-archipelago/subvibe';
|
|
8
|
-
import { selectBestTranslation, splitIntoOverlappingChunks } from '
|
|
8
|
+
import { selectBestTranslation, splitIntoOverlappingChunks } from '../../../../pathways/translate_subtitle.js';
|
|
9
9
|
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = dirname(__filename);
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// agentic.test.js
|
|
2
|
+
// Tests for the agentic entity system
|
|
3
|
+
|
|
4
|
+
import test from 'ava';
|
|
5
|
+
import serverFactory from '../../../../../index.js';
|
|
6
|
+
import { createClient } from 'graphql-ws';
|
|
7
|
+
import ws from 'ws';
|
|
8
|
+
|
|
9
|
+
// Define models to test - 4.1 as default, include grok 4
|
|
10
|
+
const TEST_MODELS = [
|
|
11
|
+
'oai-gpt41', // Default 4.1 model
|
|
12
|
+
'xai-grok-4' // Grok 4 model
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
let testServer;
|
|
16
|
+
let wsClient;
|
|
17
|
+
|
|
18
|
+
test.before(async () => {
|
|
19
|
+
process.env.CORTEX_ENABLE_REST = 'true';
|
|
20
|
+
const { server, startServer } = await serverFactory();
|
|
21
|
+
startServer && await startServer();
|
|
22
|
+
testServer = server;
|
|
23
|
+
|
|
24
|
+
// Create WebSocket client for subscriptions
|
|
25
|
+
wsClient = createClient({
|
|
26
|
+
url: `ws://localhost:${process.env.CORTEX_PORT || 4000}/graphql`,
|
|
27
|
+
webSocketImpl: ws,
|
|
28
|
+
retryAttempts: 3,
|
|
29
|
+
connectionParams: {},
|
|
30
|
+
on: {
|
|
31
|
+
error: (error) => {
|
|
32
|
+
console.error('WS connection error:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Test the connection by making a simple subscription
|
|
38
|
+
try {
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
const subscription = wsClient.subscribe(
|
|
41
|
+
{
|
|
42
|
+
query: `
|
|
43
|
+
subscription TestConnection {
|
|
44
|
+
requestProgress(requestIds: ["test"]) {
|
|
45
|
+
requestId
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
next: () => {
|
|
52
|
+
resolve();
|
|
53
|
+
},
|
|
54
|
+
error: reject,
|
|
55
|
+
complete: () => {
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Add a timeout to avoid hanging
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
resolve();
|
|
64
|
+
}, 2000);
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Failed to establish WebSocket connection:', error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test.after.always('cleanup', async () => {
|
|
73
|
+
if (wsClient) {
|
|
74
|
+
wsClient.dispose();
|
|
75
|
+
}
|
|
76
|
+
if (testServer) {
|
|
77
|
+
await testServer.stop();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Helper function to collect subscription events
|
|
82
|
+
async function collectSubscriptionEvents(subscription, timeout = 30000) {
|
|
83
|
+
const events = [];
|
|
84
|
+
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const timeoutId = setTimeout(() => {
|
|
87
|
+
if (events.length > 0) {
|
|
88
|
+
resolve(events);
|
|
89
|
+
} else {
|
|
90
|
+
reject(new Error('Subscription timed out with no events'));
|
|
91
|
+
}
|
|
92
|
+
}, timeout);
|
|
93
|
+
|
|
94
|
+
const unsubscribe = wsClient.subscribe(
|
|
95
|
+
{
|
|
96
|
+
query: subscription.query,
|
|
97
|
+
variables: subscription.variables
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
next: (event) => {
|
|
101
|
+
events.push(event);
|
|
102
|
+
if (event?.data?.requestProgress?.progress === 1) {
|
|
103
|
+
clearTimeout(timeoutId);
|
|
104
|
+
unsubscribe();
|
|
105
|
+
resolve(events);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
error: (error) => {
|
|
109
|
+
clearTimeout(timeoutId);
|
|
110
|
+
reject(error);
|
|
111
|
+
},
|
|
112
|
+
complete: () => {
|
|
113
|
+
clearTimeout(timeoutId);
|
|
114
|
+
resolve(events);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Helper function to create model-specific tests
|
|
122
|
+
function createModelTest(testName, testFunction) {
|
|
123
|
+
TEST_MODELS.forEach(model => {
|
|
124
|
+
test.serial(`${testName} - ${model}`, async (t) => {
|
|
125
|
+
await testFunction(t, model);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Helper function to validate info object structure
|
|
131
|
+
function validateInfoObject(t, info, testName) {
|
|
132
|
+
if (!info || typeof info !== 'object') {
|
|
133
|
+
t.fail(`${testName}: Info object should be a valid object`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if info object has any meaningful content
|
|
138
|
+
const hasContent = Object.keys(info).length > 0;
|
|
139
|
+
t.true(hasContent, `${testName}: Info object should have some content`);
|
|
140
|
+
|
|
141
|
+
// Validate toolUsed if present
|
|
142
|
+
if (info.toolUsed) {
|
|
143
|
+
if (Array.isArray(info.toolUsed)) {
|
|
144
|
+
t.true(info.toolUsed.length > 0, `${testName}: toolUsed array should not be empty`);
|
|
145
|
+
info.toolUsed.forEach((tool, index) => {
|
|
146
|
+
// Handle nested arrays in toolUsed
|
|
147
|
+
if (Array.isArray(tool)) {
|
|
148
|
+
t.true(tool.length > 0, `${testName}: toolUsed[${index}] nested array should not be empty`);
|
|
149
|
+
tool.forEach((nestedTool, nestedIndex) => {
|
|
150
|
+
t.true(typeof nestedTool === 'string', `${testName}: toolUsed[${index}][${nestedIndex}] should be a string`);
|
|
151
|
+
t.truthy(nestedTool.trim(), `${testName}: toolUsed[${index}][${nestedIndex}] should not be empty`);
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
t.true(typeof tool === 'string', `${testName}: toolUsed[${index}] should be a string`);
|
|
155
|
+
t.truthy(tool.trim(), `${testName}: toolUsed[${index}] should not be empty`);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
t.true(typeof info.toolUsed === 'string', `${testName}: toolUsed should be a string if not array`);
|
|
160
|
+
t.truthy(info.toolUsed.trim(), `${testName}: toolUsed should not be empty`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Validate citations if present
|
|
165
|
+
if (info.citations) {
|
|
166
|
+
t.true(Array.isArray(info.citations), `${testName}: citations should be an array`);
|
|
167
|
+
info.citations.forEach((citation, index) => {
|
|
168
|
+
t.true(typeof citation === 'object', `${testName}: citations[${index}] should be an object`);
|
|
169
|
+
if (citation.title) {
|
|
170
|
+
t.true(typeof citation.title === 'string', `${testName}: citations[${index}].title should be a string`);
|
|
171
|
+
}
|
|
172
|
+
if (citation.url) {
|
|
173
|
+
t.true(typeof citation.url === 'string', `${testName}: citations[${index}].url should be a string`);
|
|
174
|
+
// URLs can be empty strings, which is valid
|
|
175
|
+
if (citation.url.trim()) {
|
|
176
|
+
t.true(citation.url.startsWith('http'), `${testName}: citations[${index}].url should be a valid URL if not empty`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (citation.content) {
|
|
180
|
+
t.true(typeof citation.content === 'string', `${testName}: citations[${index}].content should be a string`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Validate toolCalls if present
|
|
186
|
+
if (info.toolCalls) {
|
|
187
|
+
t.true(Array.isArray(info.toolCalls), `${testName}: toolCalls should be an array`);
|
|
188
|
+
info.toolCalls.forEach((toolCall, index) => {
|
|
189
|
+
t.true(typeof toolCall === 'object', `${testName}: toolCalls[${index}] should be an object`);
|
|
190
|
+
if (toolCall.name) {
|
|
191
|
+
t.true(typeof toolCall.name === 'string', `${testName}: toolCalls[${index}].name should be a string`);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Validate usage if present
|
|
197
|
+
if (info.usage) {
|
|
198
|
+
// Handle both single usage object and array of usage objects
|
|
199
|
+
if (Array.isArray(info.usage)) {
|
|
200
|
+
t.true(info.usage.length > 0, `${testName}: usage array should not be empty`);
|
|
201
|
+
info.usage.forEach((usage, index) => {
|
|
202
|
+
t.true(typeof usage === 'object', `${testName}: usage[${index}] should be an object`);
|
|
203
|
+
if (usage.prompt_tokens !== undefined) {
|
|
204
|
+
t.true(typeof usage.prompt_tokens === 'number', `${testName}: usage[${index}].prompt_tokens should be a number`);
|
|
205
|
+
}
|
|
206
|
+
if (usage.completion_tokens !== undefined) {
|
|
207
|
+
t.true(typeof usage.completion_tokens === 'number', `${testName}: usage[${index}].completion_tokens should be a number`);
|
|
208
|
+
}
|
|
209
|
+
if (usage.total_tokens !== undefined) {
|
|
210
|
+
t.true(typeof usage.total_tokens === 'number', `${testName}: usage[${index}].total_tokens should be a number`);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
t.true(typeof info.usage === 'object', `${testName}: usage should be an object`);
|
|
215
|
+
if (info.usage.prompt_tokens !== undefined) {
|
|
216
|
+
t.true(typeof info.usage.prompt_tokens === 'number', `${testName}: usage.prompt_tokens should be a number`);
|
|
217
|
+
}
|
|
218
|
+
if (info.usage.completion_tokens !== undefined) {
|
|
219
|
+
t.true(typeof info.usage.completion_tokens === 'number', `${testName}: usage.completion_tokens should be a number`);
|
|
220
|
+
}
|
|
221
|
+
if (info.usage.total_tokens !== undefined) {
|
|
222
|
+
t.true(typeof info.usage.total_tokens === 'number', `${testName}: usage.total_tokens should be a number`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Validate finishReason if present
|
|
228
|
+
if (info.finishReason) {
|
|
229
|
+
t.true(typeof info.finishReason === 'string', `${testName}: finishReason should be a string`);
|
|
230
|
+
const validReasons = ['stop', 'length', 'tool_calls', 'content_filter', 'function_call'];
|
|
231
|
+
t.true(validReasons.includes(info.finishReason), `${testName}: finishReason should be a valid reason`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Helper function to flatten nested arrays
|
|
236
|
+
function flattenArray(arr) {
|
|
237
|
+
const result = [];
|
|
238
|
+
for (const item of arr) {
|
|
239
|
+
if (Array.isArray(item)) {
|
|
240
|
+
result.push(...flattenArray(item));
|
|
241
|
+
} else {
|
|
242
|
+
result.push(item);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Test basic single-step task
|
|
249
|
+
createModelTest('sys_entity_agent handles single-step task', async (t, model) => {
|
|
250
|
+
t.timeout(60000); // 60 second timeout
|
|
251
|
+
const response = await testServer.executeOperation({
|
|
252
|
+
query: `
|
|
253
|
+
query TestAgentSingleStep(
|
|
254
|
+
$text: String!,
|
|
255
|
+
$chatHistory: [MultiMessage]!,
|
|
256
|
+
$model: String
|
|
257
|
+
) {
|
|
258
|
+
sys_entity_agent(
|
|
259
|
+
text: $text,
|
|
260
|
+
chatHistory: $chatHistory,
|
|
261
|
+
model: $model,
|
|
262
|
+
stream: true
|
|
263
|
+
) {
|
|
264
|
+
result
|
|
265
|
+
contextId
|
|
266
|
+
tool
|
|
267
|
+
warnings
|
|
268
|
+
errors
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
`,
|
|
272
|
+
variables: {
|
|
273
|
+
text: 'What is the current time in Los Angeles?',
|
|
274
|
+
chatHistory: [{
|
|
275
|
+
role: "user",
|
|
276
|
+
content: ["What is the current time in Los Angeles?"]
|
|
277
|
+
}],
|
|
278
|
+
model: model
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
console.log(`Single-step Agent Response (${model}):`, JSON.stringify(response, null, 2));
|
|
283
|
+
|
|
284
|
+
// Check for successful response
|
|
285
|
+
t.falsy(response.body?.singleResult?.errors, 'Should not have GraphQL errors');
|
|
286
|
+
const requestId = response.body?.singleResult?.data?.sys_entity_agent?.result;
|
|
287
|
+
t.truthy(requestId, 'Should have a requestId in the result field');
|
|
288
|
+
|
|
289
|
+
// Collect events
|
|
290
|
+
const events = await collectSubscriptionEvents({
|
|
291
|
+
query: `
|
|
292
|
+
subscription OnRequestProgress($requestId: String!) {
|
|
293
|
+
requestProgress(requestIds: [$requestId]) {
|
|
294
|
+
requestId
|
|
295
|
+
progress
|
|
296
|
+
data
|
|
297
|
+
info
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
`,
|
|
301
|
+
variables: { requestId }
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
console.log(`Received ${events.length} events for single-step task (${model})`);
|
|
305
|
+
t.true(events.length > 0, 'Should have received events');
|
|
306
|
+
|
|
307
|
+
// Verify we got a completion event
|
|
308
|
+
const completionEvent = events.find(event =>
|
|
309
|
+
event.data.requestProgress.progress === 1
|
|
310
|
+
);
|
|
311
|
+
t.truthy(completionEvent, 'Should have received a completion event');
|
|
312
|
+
|
|
313
|
+
// Validate the info object in the completion event
|
|
314
|
+
const infoString = completionEvent.data.requestProgress.info;
|
|
315
|
+
|
|
316
|
+
// For single-step tasks, info might be empty or an empty object, which is acceptable
|
|
317
|
+
if (infoString && infoString.trim()) {
|
|
318
|
+
let infoObject;
|
|
319
|
+
try {
|
|
320
|
+
infoObject = JSON.parse(infoString);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
t.fail(`Failed to parse info object: ${error.message}`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Validate the info object structure
|
|
327
|
+
if (Object.keys(infoObject).length > 0) {
|
|
328
|
+
validateInfoObject(t, infoObject, 'Single-step task');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// For single-step tasks, we might not have tool usage, but we should have other properties
|
|
332
|
+
if (infoObject.finishReason) {
|
|
333
|
+
t.true(['stop', 'length', 'tool_calls', 'content_filter'].includes(infoObject.finishReason),
|
|
334
|
+
'Single-step task should have a valid finish reason');
|
|
335
|
+
}
|
|
336
|
+
console.log(`Single-step info object validation passed for ${model}:`, JSON.stringify(infoObject, null, 2));
|
|
337
|
+
} else {
|
|
338
|
+
console.log(`Single-step task (${model}) completed without info object - this is acceptable for simple tasks`);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Test multi-step task with tool usage
|
|
343
|
+
createModelTest('sys_entity_agent handles multi-step task with tools', async (t, model) => {
|
|
344
|
+
t.timeout(360000); // 120 second timeout for multi-step task
|
|
345
|
+
const response = await testServer.executeOperation({
|
|
346
|
+
query: `
|
|
347
|
+
query TestAgentMultiStep(
|
|
348
|
+
$text: String!,
|
|
349
|
+
$chatHistory: [MultiMessage]!,
|
|
350
|
+
$model: String
|
|
351
|
+
) {
|
|
352
|
+
sys_entity_agent(
|
|
353
|
+
text: $text,
|
|
354
|
+
chatHistory: $chatHistory,
|
|
355
|
+
model: $model,
|
|
356
|
+
stream: true
|
|
357
|
+
) {
|
|
358
|
+
result
|
|
359
|
+
contextId
|
|
360
|
+
tool
|
|
361
|
+
warnings
|
|
362
|
+
errors
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
`,
|
|
366
|
+
variables: {
|
|
367
|
+
text: 'Research the latest developments in renewable energy and summarize the key trends.',
|
|
368
|
+
chatHistory: [{
|
|
369
|
+
role: "user",
|
|
370
|
+
content: ["Research the latest developments in renewable energy and summarize the key trends."]
|
|
371
|
+
}],
|
|
372
|
+
model: model
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
console.log(`Multi-step Agent Response (${model}):`, JSON.stringify(response, null, 2));
|
|
377
|
+
|
|
378
|
+
// Check for successful response
|
|
379
|
+
t.falsy(response.body?.singleResult?.errors, 'Should not have GraphQL errors');
|
|
380
|
+
const requestId = response.body?.singleResult?.data?.sys_entity_agent?.result;
|
|
381
|
+
t.truthy(requestId, 'Should have a requestId in the result field');
|
|
382
|
+
|
|
383
|
+
// Collect events with a longer timeout since this is a multi-step operation
|
|
384
|
+
const events = await collectSubscriptionEvents({
|
|
385
|
+
query: `
|
|
386
|
+
subscription OnRequestProgress($requestId: String!) {
|
|
387
|
+
requestProgress(requestIds: [$requestId]) {
|
|
388
|
+
requestId
|
|
389
|
+
progress
|
|
390
|
+
data
|
|
391
|
+
info
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
`,
|
|
395
|
+
variables: { requestId }
|
|
396
|
+
}, 240000);
|
|
397
|
+
|
|
398
|
+
console.log(`Received ${events.length} events for multi-step task (${model})`);
|
|
399
|
+
t.true(events.length > 0, 'Should have received events');
|
|
400
|
+
|
|
401
|
+
// Verify we got a completion event
|
|
402
|
+
const completionEvent = events.find(event =>
|
|
403
|
+
event.data.requestProgress.progress === 1
|
|
404
|
+
);
|
|
405
|
+
t.truthy(completionEvent, 'Should have received a completion event');
|
|
406
|
+
|
|
407
|
+
const infoString = completionEvent.data.requestProgress.info;
|
|
408
|
+
t.truthy(infoString, 'Multi-step task should have info object');
|
|
409
|
+
|
|
410
|
+
let infoObject;
|
|
411
|
+
try {
|
|
412
|
+
infoObject = JSON.parse(infoString);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
t.fail(`Failed to parse info object: ${error.message}`);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Validate the info object structure
|
|
419
|
+
validateInfoObject(t, infoObject, 'Multi-step task');
|
|
420
|
+
|
|
421
|
+
// Additional specific validations for multi-step tasks
|
|
422
|
+
if (infoObject.toolUsed) {
|
|
423
|
+
// For multi-step tasks, we expect multiple tools to be used
|
|
424
|
+
const toolUsedArray = Array.isArray(infoObject.toolUsed) ? infoObject.toolUsed : [infoObject.toolUsed];
|
|
425
|
+
t.true(toolUsedArray.length > 0, 'Multi-step task should have used at least one tool');
|
|
426
|
+
|
|
427
|
+
// Flatten nested arrays for tool counting
|
|
428
|
+
const flattenedTools = flattenArray(toolUsedArray);
|
|
429
|
+
t.true(flattenedTools.length > 0, 'Multi-step task should have used tools after flattening');
|
|
430
|
+
|
|
431
|
+
// Check for common tool types that should be used in research tasks
|
|
432
|
+
const expectedToolTypes = ['Search', 'SearchInternetAgent2', 'SearchXPlatform', 'WebPageContent', 'SearchAgent'];
|
|
433
|
+
const hasExpectedTool = flattenedTools.some(tool =>
|
|
434
|
+
expectedToolTypes.some(expectedType => tool.includes(expectedType))
|
|
435
|
+
);
|
|
436
|
+
t.true(hasExpectedTool, 'Multi-step research task should have used search tools');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Validate citations for research tasks
|
|
440
|
+
if (infoObject.citations) {
|
|
441
|
+
t.true(infoObject.citations.length > 0, 'Research task should have citations');
|
|
442
|
+
infoObject.citations.forEach((citation, index) => {
|
|
443
|
+
// Citations should have either URL or content, but not necessarily both
|
|
444
|
+
t.truthy(citation.url || citation.content, `Citation ${index} should have URL or content`);
|
|
445
|
+
if (citation.title || citation.content) {
|
|
446
|
+
t.truthy(citation.title || citation.content, `Citation ${index} should have title or content`);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Validate toolCalls for multi-step tasks
|
|
452
|
+
if (infoObject.toolCalls) {
|
|
453
|
+
t.true(infoObject.toolCalls.length > 0, 'Multi-step task should have tool calls');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Validate usage statistics
|
|
457
|
+
if (infoObject.usage) {
|
|
458
|
+
// Handle both single usage object and array of usage objects
|
|
459
|
+
if (Array.isArray(infoObject.usage)) {
|
|
460
|
+
t.true(infoObject.usage.length > 0, 'Multi-step task should have usage data');
|
|
461
|
+
const latestUsage = infoObject.usage[0]; // Most recent usage first
|
|
462
|
+
t.truthy(latestUsage.total_tokens, 'Multi-step task should have total token usage');
|
|
463
|
+
t.true(latestUsage.total_tokens > 0, 'Multi-step task should have used tokens');
|
|
464
|
+
} else {
|
|
465
|
+
t.truthy(infoObject.usage.total_tokens, 'Multi-step task should have total token usage');
|
|
466
|
+
t.true(infoObject.usage.total_tokens > 0, 'Multi-step task should have used tokens');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Validate finish reason
|
|
471
|
+
if (infoObject.finishReason) {
|
|
472
|
+
t.true(['stop', 'length', 'tool_calls', 'content_filter'].includes(infoObject.finishReason),
|
|
473
|
+
'Multi-step task should have a valid finish reason');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log(`Multi-step info object validation passed for ${model}:`, JSON.stringify(infoObject, null, 2));
|
|
477
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Consolidated GraphQL subscription streaming tests (subset from subscription.test.js)
|
|
2
|
+
|
|
3
|
+
import test from 'ava';
|
|
4
|
+
import serverFactory from '../../../../../index.js';
|
|
5
|
+
import { createWsClient, ensureWsConnection, collectSubscriptionEvents, validateProgressMessage } from '../../../../helpers/subscriptions.js';
|
|
6
|
+
|
|
7
|
+
let testServer;
|
|
8
|
+
let wsClient;
|
|
9
|
+
|
|
10
|
+
test.before(async () => {
|
|
11
|
+
process.env.CORTEX_ENABLE_REST = 'true';
|
|
12
|
+
const { server, startServer } = await serverFactory();
|
|
13
|
+
startServer && await startServer();
|
|
14
|
+
testServer = server;
|
|
15
|
+
|
|
16
|
+
wsClient = createWsClient();
|
|
17
|
+
await ensureWsConnection(wsClient);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test.after.always('cleanup', async () => {
|
|
21
|
+
if (wsClient) wsClient.dispose();
|
|
22
|
+
if (testServer) await testServer.stop();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test.serial('Request progress messages have string data and info fields', async (t) => {
|
|
26
|
+
const response = await testServer.executeOperation({
|
|
27
|
+
query: `
|
|
28
|
+
query TestQuery($text: String!) {
|
|
29
|
+
chat(text: $text, async: true, stream: true) {
|
|
30
|
+
result
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`,
|
|
34
|
+
variables: { text: 'Generate a long response to test streaming' }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const requestId = response.body?.singleResult?.data?.chat?.result;
|
|
38
|
+
t.truthy(requestId);
|
|
39
|
+
|
|
40
|
+
const events = await collectSubscriptionEvents(wsClient, {
|
|
41
|
+
query: `
|
|
42
|
+
subscription OnRequestProgress($requestId: String!) {
|
|
43
|
+
requestProgress(requestIds: [$requestId]) {
|
|
44
|
+
requestId
|
|
45
|
+
progress
|
|
46
|
+
data
|
|
47
|
+
info
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`,
|
|
51
|
+
variables: { requestId }
|
|
52
|
+
}, 10000, { requireCompletion: false, minEvents: 1 });
|
|
53
|
+
|
|
54
|
+
t.true(events.length > 0);
|
|
55
|
+
|
|
56
|
+
for (const event of events) {
|
|
57
|
+
const progress = event.data.requestProgress;
|
|
58
|
+
validateProgressMessage(t, progress, requestId);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
|