@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.
Files changed (211) hide show
  1. package/.github/workflows/cortex-file-handler-test.yml +61 -0
  2. package/README.md +31 -7
  3. package/config/default.example.json +15 -0
  4. package/config.js +133 -12
  5. package/helper-apps/cortex-autogen2/DigiCertGlobalRootCA.crt.pem +22 -0
  6. package/helper-apps/cortex-autogen2/Dockerfile +31 -0
  7. package/helper-apps/cortex-autogen2/Dockerfile.worker +41 -0
  8. package/helper-apps/cortex-autogen2/README.md +183 -0
  9. package/helper-apps/cortex-autogen2/__init__.py +1 -0
  10. package/helper-apps/cortex-autogen2/agents.py +131 -0
  11. package/helper-apps/cortex-autogen2/docker-compose.yml +20 -0
  12. package/helper-apps/cortex-autogen2/function_app.py +55 -0
  13. package/helper-apps/cortex-autogen2/host.json +15 -0
  14. package/helper-apps/cortex-autogen2/main.py +126 -0
  15. package/helper-apps/cortex-autogen2/poetry.lock +3652 -0
  16. package/helper-apps/cortex-autogen2/pyproject.toml +36 -0
  17. package/helper-apps/cortex-autogen2/requirements.txt +20 -0
  18. package/helper-apps/cortex-autogen2/send_task.py +105 -0
  19. package/helper-apps/cortex-autogen2/services/__init__.py +1 -0
  20. package/helper-apps/cortex-autogen2/services/azure_queue.py +85 -0
  21. package/helper-apps/cortex-autogen2/services/redis_publisher.py +153 -0
  22. package/helper-apps/cortex-autogen2/task_processor.py +488 -0
  23. package/helper-apps/cortex-autogen2/tools/__init__.py +24 -0
  24. package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +175 -0
  25. package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +601 -0
  26. package/helper-apps/cortex-autogen2/tools/coding_tools.py +72 -0
  27. package/helper-apps/cortex-autogen2/tools/download_tools.py +48 -0
  28. package/helper-apps/cortex-autogen2/tools/file_tools.py +545 -0
  29. package/helper-apps/cortex-autogen2/tools/search_tools.py +646 -0
  30. package/helper-apps/cortex-azure-cleaner/README.md +36 -0
  31. package/helper-apps/cortex-file-converter/README.md +93 -0
  32. package/helper-apps/cortex-file-converter/key_to_pdf.py +104 -0
  33. package/helper-apps/cortex-file-converter/list_blob_extensions.py +89 -0
  34. package/helper-apps/cortex-file-converter/process_azure_keynotes.py +181 -0
  35. package/helper-apps/cortex-file-converter/requirements.txt +1 -0
  36. package/helper-apps/cortex-file-handler/.env.test.azure.ci +7 -0
  37. package/helper-apps/cortex-file-handler/.env.test.azure.sample +1 -1
  38. package/helper-apps/cortex-file-handler/.env.test.gcs.ci +10 -0
  39. package/helper-apps/cortex-file-handler/.env.test.gcs.sample +2 -2
  40. package/helper-apps/cortex-file-handler/INTERFACE.md +41 -0
  41. package/helper-apps/cortex-file-handler/package.json +1 -1
  42. package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +41 -17
  43. package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +30 -15
  44. package/helper-apps/cortex-file-handler/scripts/test-azure.sh +32 -6
  45. package/helper-apps/cortex-file-handler/scripts/test-gcs.sh +24 -2
  46. package/helper-apps/cortex-file-handler/scripts/validate-env.js +128 -0
  47. package/helper-apps/cortex-file-handler/src/blobHandler.js +161 -51
  48. package/helper-apps/cortex-file-handler/src/constants.js +3 -0
  49. package/helper-apps/cortex-file-handler/src/fileChunker.js +10 -8
  50. package/helper-apps/cortex-file-handler/src/index.js +116 -9
  51. package/helper-apps/cortex-file-handler/src/redis.js +61 -1
  52. package/helper-apps/cortex-file-handler/src/services/ConversionService.js +11 -8
  53. package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +2 -2
  54. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +88 -6
  55. package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +58 -0
  56. package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +25 -5
  57. package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +9 -0
  58. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +120 -16
  59. package/helper-apps/cortex-file-handler/src/start.js +27 -17
  60. package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +52 -1
  61. package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +40 -0
  62. package/helper-apps/cortex-file-handler/tests/checkHashShortLived.test.js +553 -0
  63. package/helper-apps/cortex-file-handler/tests/cleanup.test.js +46 -52
  64. package/helper-apps/cortex-file-handler/tests/containerConversionFlow.test.js +451 -0
  65. package/helper-apps/cortex-file-handler/tests/containerNameParsing.test.js +229 -0
  66. package/helper-apps/cortex-file-handler/tests/containerParameterFlow.test.js +392 -0
  67. package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +7 -2
  68. package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +348 -0
  69. package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +23 -2
  70. package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +11 -5
  71. package/helper-apps/cortex-file-handler/tests/getOperations.test.js +58 -24
  72. package/helper-apps/cortex-file-handler/tests/postOperations.test.js +11 -4
  73. package/helper-apps/cortex-file-handler/tests/shortLivedUrlConversion.test.js +225 -0
  74. package/helper-apps/cortex-file-handler/tests/start.test.js +8 -12
  75. package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +80 -0
  76. package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +388 -22
  77. package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +74 -0
  78. package/lib/cortexResponse.js +153 -0
  79. package/lib/entityConstants.js +21 -3
  80. package/lib/logger.js +21 -4
  81. package/lib/pathwayTools.js +28 -9
  82. package/lib/util.js +49 -0
  83. package/package.json +1 -1
  84. package/pathways/basePathway.js +1 -0
  85. package/pathways/bing_afagent.js +54 -1
  86. package/pathways/call_tools.js +2 -3
  87. package/pathways/chat_jarvis.js +1 -1
  88. package/pathways/google_cse.js +27 -0
  89. package/pathways/grok_live_search.js +18 -0
  90. package/pathways/system/entity/memory/sys_memory_lookup_required.js +1 -0
  91. package/pathways/system/entity/memory/sys_memory_required.js +1 -0
  92. package/pathways/system/entity/memory/sys_search_memory.js +1 -0
  93. package/pathways/system/entity/sys_entity_agent.js +56 -4
  94. package/pathways/system/entity/sys_generator_quick.js +1 -0
  95. package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +26 -0
  96. package/pathways/system/entity/tools/sys_tool_google_search.js +141 -0
  97. package/pathways/system/entity/tools/sys_tool_grok_x_search.js +237 -0
  98. package/pathways/system/entity/tools/sys_tool_image.js +1 -1
  99. package/pathways/system/rest_streaming/sys_claude_37_sonnet.js +21 -0
  100. package/pathways/system/rest_streaming/sys_claude_41_opus.js +21 -0
  101. package/pathways/system/rest_streaming/sys_claude_4_sonnet.js +21 -0
  102. package/pathways/system/rest_streaming/sys_google_gemini_25_flash.js +25 -0
  103. package/pathways/system/rest_streaming/{sys_google_gemini_chat.js → sys_google_gemini_25_pro.js} +6 -4
  104. package/pathways/system/rest_streaming/sys_grok_4.js +23 -0
  105. package/pathways/system/rest_streaming/sys_grok_4_fast_non_reasoning.js +23 -0
  106. package/pathways/system/rest_streaming/sys_grok_4_fast_reasoning.js +23 -0
  107. package/pathways/system/rest_streaming/sys_openai_chat.js +3 -0
  108. package/pathways/system/rest_streaming/sys_openai_chat_gpt41.js +22 -0
  109. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_mini.js +21 -0
  110. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_nano.js +21 -0
  111. package/pathways/system/rest_streaming/{sys_claude_35_sonnet.js → sys_openai_chat_gpt4_omni.js} +6 -4
  112. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_omni_mini.js +21 -0
  113. package/pathways/system/rest_streaming/{sys_claude_3_haiku.js → sys_openai_chat_gpt5.js} +7 -5
  114. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_chat.js +21 -0
  115. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_mini.js +21 -0
  116. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_nano.js +21 -0
  117. package/pathways/system/rest_streaming/{sys_openai_chat_o1.js → sys_openai_chat_o3.js} +6 -3
  118. package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +3 -0
  119. package/pathways/system/workspaces/run_workspace_prompt.js +99 -0
  120. package/pathways/vision.js +1 -1
  121. package/server/graphql.js +1 -1
  122. package/server/modelExecutor.js +8 -0
  123. package/server/pathwayResolver.js +166 -16
  124. package/server/pathwayResponseParser.js +16 -8
  125. package/server/plugins/azureFoundryAgentsPlugin.js +1 -1
  126. package/server/plugins/claude3VertexPlugin.js +193 -45
  127. package/server/plugins/gemini15ChatPlugin.js +21 -0
  128. package/server/plugins/gemini15VisionPlugin.js +360 -0
  129. package/server/plugins/googleCsePlugin.js +94 -0
  130. package/server/plugins/grokVisionPlugin.js +365 -0
  131. package/server/plugins/modelPlugin.js +3 -1
  132. package/server/plugins/openAiChatPlugin.js +106 -13
  133. package/server/plugins/openAiVisionPlugin.js +42 -30
  134. package/server/resolver.js +28 -4
  135. package/server/rest.js +270 -53
  136. package/server/typeDef.js +1 -0
  137. package/tests/{mocks.js → helpers/mocks.js} +5 -2
  138. package/tests/{server.js → helpers/server.js} +2 -2
  139. package/tests/helpers/sseAssert.js +23 -0
  140. package/tests/helpers/sseClient.js +73 -0
  141. package/tests/helpers/subscriptionAssert.js +11 -0
  142. package/tests/helpers/subscriptions.js +113 -0
  143. package/tests/{sublong.srt → integration/features/translate/sublong.srt} +4543 -4543
  144. package/tests/integration/features/translate/translate_chunking_stream.test.js +100 -0
  145. package/tests/{translate_srt.test.js → integration/features/translate/translate_srt.test.js} +2 -2
  146. package/tests/integration/graphql/async/stream/agentic.test.js +477 -0
  147. package/tests/integration/graphql/async/stream/subscription_streaming.test.js +62 -0
  148. package/tests/integration/graphql/async/stream/sys_entity_start_streaming.test.js +71 -0
  149. package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +56 -0
  150. package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +66 -0
  151. package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +56 -0
  152. package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +72 -0
  153. package/tests/integration/graphql/features/google/sysToolGoogleSearch.test.js +96 -0
  154. package/tests/integration/graphql/features/grok/grok.test.js +688 -0
  155. package/tests/integration/graphql/features/grok/grok_x_search_tool.test.js +354 -0
  156. package/tests/{main.test.js → integration/graphql/features/main.test.js} +1 -1
  157. package/tests/{call_tools.test.js → integration/graphql/features/tools/call_tools.test.js} +2 -2
  158. package/tests/{vision.test.js → integration/graphql/features/vision/vision.test.js} +1 -1
  159. package/tests/integration/graphql/subscriptions/connection.test.js +26 -0
  160. package/tests/{openai_api.test.js → integration/rest/oai/openai_api.test.js} +63 -238
  161. package/tests/integration/rest/oai/tool_calling_api.test.js +343 -0
  162. package/tests/integration/rest/oai/tool_calling_streaming.test.js +85 -0
  163. package/tests/integration/rest/vendors/claude_streaming.test.js +47 -0
  164. package/tests/integration/rest/vendors/claude_tool_calling_streaming.test.js +75 -0
  165. package/tests/integration/rest/vendors/gemini_streaming.test.js +47 -0
  166. package/tests/integration/rest/vendors/gemini_tool_calling_streaming.test.js +75 -0
  167. package/tests/integration/rest/vendors/grok_streaming.test.js +55 -0
  168. package/tests/integration/rest/vendors/grok_tool_calling_streaming.test.js +75 -0
  169. package/tests/{azureAuthTokenHelper.test.js → unit/core/azureAuthTokenHelper.test.js} +1 -1
  170. package/tests/{chunkfunction.test.js → unit/core/chunkfunction.test.js} +2 -2
  171. package/tests/{config.test.js → unit/core/config.test.js} +3 -3
  172. package/tests/{encodeCache.test.js → unit/core/encodeCache.test.js} +1 -1
  173. package/tests/{fastLruCache.test.js → unit/core/fastLruCache.test.js} +1 -1
  174. package/tests/{handleBars.test.js → unit/core/handleBars.test.js} +1 -1
  175. package/tests/{memoryfunction.test.js → unit/core/memoryfunction.test.js} +2 -2
  176. package/tests/unit/core/mergeResolver.test.js +952 -0
  177. package/tests/{parser.test.js → unit/core/parser.test.js} +3 -3
  178. package/tests/unit/core/pathwayResolver.test.js +187 -0
  179. package/tests/{requestMonitor.test.js → unit/core/requestMonitor.test.js} +1 -1
  180. package/tests/{requestMonitorDurationEstimator.test.js → unit/core/requestMonitorDurationEstimator.test.js} +1 -1
  181. package/tests/{truncateMessages.test.js → unit/core/truncateMessages.test.js} +3 -3
  182. package/tests/{util.test.js → unit/core/util.test.js} +1 -1
  183. package/tests/{apptekTranslatePlugin.test.js → unit/plugins/apptekTranslatePlugin.test.js} +3 -3
  184. package/tests/{azureFoundryAgents.test.js → unit/plugins/azureFoundryAgents.test.js} +136 -1
  185. package/tests/{claude3VertexPlugin.test.js → unit/plugins/claude3VertexPlugin.test.js} +32 -10
  186. package/tests/{claude3VertexToolConversion.test.js → unit/plugins/claude3VertexToolConversion.test.js} +3 -3
  187. package/tests/unit/plugins/googleCsePlugin.test.js +111 -0
  188. package/tests/unit/plugins/grokVisionPlugin.test.js +1392 -0
  189. package/tests/{modelPlugin.test.js → unit/plugins/modelPlugin.test.js} +3 -3
  190. package/tests/{multimodal_conversion.test.js → unit/plugins/multimodal_conversion.test.js} +4 -4
  191. package/tests/{openAiChatPlugin.test.js → unit/plugins/openAiChatPlugin.test.js} +13 -4
  192. package/tests/{openAiToolPlugin.test.js → unit/plugins/openAiToolPlugin.test.js} +35 -27
  193. package/tests/{tokenHandlingTests.test.js → unit/plugins/tokenHandlingTests.test.js} +5 -5
  194. package/tests/{translate_apptek.test.js → unit/plugins/translate_apptek.test.js} +3 -3
  195. package/tests/{streaming.test.js → unit/plugins.streaming/plugin_stream_events.test.js} +19 -58
  196. package/helper-apps/mogrt-handler/tests/test-files/test.gif +0 -1
  197. package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +0 -1
  198. package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +0 -1
  199. package/pathways/system/rest_streaming/sys_openai_chat_gpt4.js +0 -19
  200. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_32.js +0 -19
  201. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_turbo.js +0 -19
  202. package/pathways/system/workspaces/run_claude35_sonnet.js +0 -21
  203. package/pathways/system/workspaces/run_claude3_haiku.js +0 -20
  204. package/pathways/system/workspaces/run_gpt35turbo.js +0 -20
  205. package/pathways/system/workspaces/run_gpt4.js +0 -20
  206. package/pathways/system/workspaces/run_gpt4_32.js +0 -20
  207. package/tests/agentic.test.js +0 -256
  208. package/tests/pathwayResolver.test.js +0 -78
  209. package/tests/subscription.test.js +0 -387
  210. /package/tests/{subchunk.srt → integration/features/translate/subchunk.srt} +0 -0
  211. /package/tests/{subhorizontal.srt → integration/features/translate/subhorizontal.srt} +0 -0
@@ -1,5 +1,7 @@
1
1
  import { fulfillWithTimeout } from '../lib/promiser.js';
2
2
  import { PathwayResolver } from './pathwayResolver.js';
3
+ import CortexResponse from '../lib/cortexResponse.js';
4
+ import { withRequestLoggingDisabled } from '../lib/logger.js';
3
5
 
4
6
  // This resolver uses standard parameters required by Apollo server:
5
7
  // (parent, args, contextValue, info)
@@ -19,18 +21,40 @@ const rootResolver = async (parent, args, contextValue, info) => {
19
21
  let result = null;
20
22
 
21
23
  try {
22
- result = await fulfillWithTimeout(pathway.resolver(parent, args, contextValue, info), pathway.timeout);
24
+ const execWithTimeout = () => fulfillWithTimeout(pathway.resolver(parent, args, contextValue, info), pathway.timeout);
25
+ if (pathway.requestLoggingDisabled === true) {
26
+ result = await withRequestLoggingDisabled(() => execWithTimeout());
27
+ } else {
28
+ result = await execWithTimeout();
29
+ }
23
30
  } catch (error) {
24
31
  pathwayResolver.logError(error);
25
32
  result = error.message || error.toString();
26
33
  }
34
+
35
+ if (result instanceof CortexResponse) {
36
+ // Use the smart mergeResultData method that handles CortexResponse objects
37
+ pathwayResolver.pathwayResultData = pathwayResolver.mergeResultData(result);
38
+ result = result.output_text;
39
+ }
40
+
41
+ let resultData = pathwayResolver.pathwayResultData ? JSON.stringify(pathwayResolver.pathwayResultData) : null;
27
42
 
28
- const { warnings, errors, previousResult, savedContextId, tool } = pathwayResolver;
29
-
43
+ const { warnings, errors, previousResult, savedContextId, tool } = pathwayResolver;
44
+
30
45
  // Add request parameters back as debug
31
46
  const debug = pathwayResolver.prompts.map(prompt => prompt.debugInfo || '').join('\n').trim();
32
47
 
33
- return { debug, result, warnings, errors, previousResult, tool, contextId: savedContextId }
48
+ return {
49
+ debug,
50
+ result,
51
+ resultData,
52
+ warnings,
53
+ errors,
54
+ previousResult,
55
+ tool,
56
+ contextId: savedContextId
57
+ }
34
58
  }
35
59
 
36
60
  // This resolver is used by the root resolver to process the request
package/server/rest.js CHANGED
@@ -36,6 +36,101 @@ const chunkTextIntoTokens = (() => {
36
36
  };
37
37
  })();
38
38
 
39
+ // Helper functions to reduce code duplication
40
+ const resolveModelName = (modelName, openAIChatModels, openAICompletionModels, isChat = false) => {
41
+ if (modelName.startsWith('ollama-')) {
42
+ const pathwayName = isChat ? 'sys_ollama_chat' : 'sys_ollama_completion';
43
+ return { pathwayName, isOllama: true };
44
+ } else {
45
+ const modelMap = isChat ? openAIChatModels : openAICompletionModels;
46
+ const pathwayName = modelMap[modelName] || modelMap['*'];
47
+ return { pathwayName, isOllama: false };
48
+ }
49
+ };
50
+
51
+ const handleModelNotFound = (res, modelName) => {
52
+ res.status(404).json({
53
+ error: `Model ${modelName} not found.`,
54
+ });
55
+ };
56
+
57
+ const extractResponseData = (pathwayResponse) => {
58
+ if (typeof pathwayResponse === 'string') {
59
+ return { resultText: pathwayResponse, resultData: null };
60
+ }
61
+ return {
62
+ resultText: pathwayResponse.result || "",
63
+ resultData: pathwayResponse.resultData || null
64
+ };
65
+ };
66
+
67
+ const parseToolCalls = (resultData, resultText) => {
68
+ let messageContent = resultText;
69
+ let toolCalls = null;
70
+ let functionCall = null;
71
+ let finishReason = 'stop';
72
+
73
+ // First check if we have structured response data from the pathway response
74
+ if (resultData) {
75
+ try {
76
+ const parsedResultData = typeof resultData === 'string' ? JSON.parse(resultData) : resultData;
77
+
78
+ // resultData contains the full CortexResponse object
79
+ if (parsedResultData && parsedResultData.toolCalls) {
80
+ toolCalls = parsedResultData.toolCalls;
81
+ finishReason = 'tool_calls';
82
+ } else if (parsedResultData && parsedResultData.functionCall) {
83
+ functionCall = parsedResultData.functionCall;
84
+ finishReason = 'function_call';
85
+ }
86
+ } catch (e) {
87
+ // If parsing structured response fails, continue with regular parsing
88
+ }
89
+ }
90
+
91
+ // If no tool data found, try parsing the result text as before for backward compatibility
92
+ if (!toolCalls && !functionCall) {
93
+ try {
94
+ const parsedResponse = JSON.parse(resultText);
95
+
96
+ // Check if this is a tool calls response
97
+ if (parsedResponse.role === 'assistant' && parsedResponse.hasOwnProperty('tool_calls')) {
98
+ if (parsedResponse.tool_calls) {
99
+ toolCalls = parsedResponse.tool_calls;
100
+ messageContent = parsedResponse.content || "";
101
+ finishReason = 'tool_calls';
102
+ }
103
+ } else if (parsedResponse.tool_calls) {
104
+ toolCalls = parsedResponse.tool_calls;
105
+ messageContent = parsedResponse.content || "";
106
+ finishReason = 'tool_calls';
107
+ }
108
+ // Check if this is a legacy function call response
109
+ else if (parsedResponse.role === 'assistant' && parsedResponse.hasOwnProperty('function_call')) {
110
+ if (parsedResponse.function_call) {
111
+ functionCall = parsedResponse.function_call;
112
+ messageContent = parsedResponse.content || "";
113
+ finishReason = 'function_call';
114
+ }
115
+ } else if (parsedResponse.function_call) {
116
+ functionCall = parsedResponse.function_call;
117
+ messageContent = parsedResponse.content || "";
118
+ finishReason = 'function_call';
119
+ }
120
+ } catch (e) {
121
+ // If parsing fails, treat as regular text response
122
+ messageContent = resultText;
123
+ }
124
+ }
125
+
126
+ return { messageContent, toolCalls, functionCall, finishReason };
127
+ };
128
+
129
+ const generateResponseId = (prefix) => {
130
+ const requestId = uuidv4();
131
+ return `${prefix}-${requestId}`;
132
+ };
133
+
39
134
  const processRestRequest = async (server, req, pathway, name, parameterMap = {}) => {
40
135
  const fieldVariableDefs = pathway.typeDef(pathway).restDefinition || [];
41
136
 
@@ -51,6 +146,8 @@ const processRestRequest = async (server, req, pathway, name, parameterMap = {})
51
146
  msg.content.map(item => JSON.stringify(item)) :
52
147
  msg.content
53
148
  }));
149
+ } else if (type === '[String]' && Array.isArray(value)) {
150
+ return value;
54
151
  } else {
55
152
  return value;
56
153
  }
@@ -67,6 +164,20 @@ const processRestRequest = async (server, req, pathway, name, parameterMap = {})
67
164
  return acc;
68
165
  }, {});
69
166
 
167
+ // Add tools to variables if they exist in the request
168
+ if (req.body.tools) {
169
+ variables.tools = JSON.stringify(req.body.tools);
170
+ }
171
+
172
+ if (req.body.tool_choice) {
173
+ variables.tool_choice = typeof req.body.tool_choice === 'string' ? req.body.tool_choice : JSON.stringify(req.body.tool_choice);
174
+ }
175
+
176
+ // Add functions to variables if they exist in the request (legacy function calling)
177
+ if (req.body.functions) {
178
+ variables.functions = JSON.stringify(req.body.functions);
179
+ }
180
+
70
181
  const variableParams = fieldVariableDefs.map(({ name, type }) => `$${name}: ${type}`).join(', ');
71
182
  const queryArgs = fieldVariableDefs.map(({ name }) => `${name}: $${name}`).join(', ');
72
183
 
@@ -76,10 +187,19 @@ const processRestRequest = async (server, req, pathway, name, parameterMap = {})
76
187
  contextId
77
188
  previousResult
78
189
  result
190
+ resultData
191
+ tool
192
+ warnings
193
+ errors
194
+ debug
79
195
  }
80
196
  }
81
197
  `;
82
198
 
199
+ // Debug: Log the variables being passed
200
+ logger.debug(`REST endpoint variables: ${JSON.stringify(variables, null, 2)}`);
201
+ logger.debug(`REST endpoint query: ${query}`);
202
+
83
203
  const result = await server.executeOperation({ query, variables });
84
204
 
85
205
  // if we're streaming and there are errors, we return a standard error code
@@ -89,9 +209,27 @@ const processRestRequest = async (server, req, pathway, name, parameterMap = {})
89
209
  }
90
210
  }
91
211
 
92
- // otherwise errors can just be returned as a string
93
- const resultText = result?.body?.singleResult?.data?.[name]?.result || result?.body?.singleResult?.errors?.[0]?.message || "";
94
- return resultText;
212
+ // For non-streaming, return both result and tool fields
213
+ const pathwayData = result?.body?.singleResult?.data?.[name];
214
+ if (pathwayData) {
215
+ return {
216
+ result: pathwayData.result || "",
217
+ resultData: pathwayData.resultData || null,
218
+ tool: pathwayData.tool || null,
219
+ errors: pathwayData.errors || null,
220
+ warnings: pathwayData.warnings || null
221
+ };
222
+ }
223
+
224
+ // If no pathway data, return error message
225
+ const errorMessage = result?.body?.singleResult?.errors?.[0]?.message || "";
226
+ return {
227
+ result: errorMessage,
228
+ resultData: null,
229
+ tool: null,
230
+ errors: errorMessage ? [errorMessage] : null,
231
+ warnings: null
232
+ };
95
233
  };
96
234
 
97
235
  const processIncomingStream = (requestId, res, jsonResponse, pathway) => {
@@ -150,12 +288,30 @@ const processIncomingStream = (requestId, res, jsonResponse, pathway) => {
150
288
  if (jsonResponse.object === 'text_completion') {
151
289
  jsonResponse.choices[0].text = inputText;
152
290
  } else {
291
+ // Ensure delta object exists
292
+ if (!jsonResponse.choices[0].delta) {
293
+ jsonResponse.choices[0].delta = {};
294
+ }
153
295
  jsonResponse.choices[0].delta.content = inputText;
154
296
  }
155
297
 
156
298
  return jsonResponse;
157
299
  }
158
300
 
301
+ const fillJsonResponseWithToolCalls = (jsonResponse, toolCalls, finishReason) => {
302
+ jsonResponse.choices[0].finish_reason = finishReason;
303
+ if (jsonResponse.object === 'text_completion') {
304
+ // Handle text completion tool calls if needed
305
+ } else {
306
+ // Ensure delta object exists
307
+ if (!jsonResponse.choices[0].delta) {
308
+ jsonResponse.choices[0].delta = {};
309
+ }
310
+ jsonResponse.choices[0].delta.tool_calls = toolCalls;
311
+ }
312
+ return jsonResponse;
313
+ }
314
+
159
315
  startStream(res);
160
316
 
161
317
  // If the requestId is an error message, we can't continue
@@ -191,6 +347,21 @@ const processIncomingStream = (requestId, res, jsonResponse, pathway) => {
191
347
  return;
192
348
  }
193
349
 
350
+ // Check if this is a tool call response
351
+ try {
352
+ const parsedData = JSON.parse(stringData);
353
+ if (parsedData.tool_calls) {
354
+ // Send tool calls as a single chunk
355
+ fillJsonResponseWithToolCalls(jsonResponse, parsedData.tool_calls, "tool_calls");
356
+ sendStreamData(jsonResponse);
357
+ safeUnsubscribe();
358
+ finishStream(res, jsonResponse);
359
+ return;
360
+ }
361
+ } catch (e) {
362
+ // Not JSON, treat as regular text
363
+ }
364
+
194
365
  chunkTextIntoTokens(stringData, false, useSingleTokenStream).forEach(token => {
195
366
  fillJsonResponse(jsonResponse, token, null);
196
367
  sendStreamData(jsonResponse);
@@ -224,6 +395,67 @@ const processIncomingStream = (requestId, res, jsonResponse, pathway) => {
224
395
  return;
225
396
  }
226
397
 
398
+ // Check if this is a streaming event with tool calls
399
+ if (messageJson.choices && messageJson.choices[0] && messageJson.choices[0].delta) {
400
+ const delta = messageJson.choices[0].delta;
401
+ const finishReason = messageJson.choices[0].finish_reason;
402
+
403
+ // Handle tool calls in streaming events
404
+ if (delta.tool_calls) {
405
+ fillJsonResponseWithToolCalls(jsonResponse, delta.tool_calls, finishReason || "tool_calls");
406
+ sendStreamData(jsonResponse);
407
+
408
+ if (finishReason === "tool_calls" || progress === 1) {
409
+ safeUnsubscribe();
410
+ finishStream(res, jsonResponse);
411
+ }
412
+ return;
413
+ }
414
+
415
+ // Handle the case where we get an empty delta with finish_reason: "tool_calls"
416
+ if (finishReason === "tool_calls" && Object.keys(delta).length === 0) {
417
+
418
+ safeUnsubscribe();
419
+ finishStream(res, jsonResponse);
420
+ return;
421
+ }
422
+
423
+ // Handle function calls in streaming events
424
+ if (delta.function_call) {
425
+ // Ensure delta object exists
426
+ if (!jsonResponse.choices[0].delta) {
427
+ jsonResponse.choices[0].delta = {};
428
+ }
429
+ jsonResponse.choices[0].delta.function_call = delta.function_call;
430
+ jsonResponse.choices[0].finish_reason = finishReason || "function_call";
431
+ sendStreamData(jsonResponse);
432
+
433
+ if (finishReason === "function_call") {
434
+ safeUnsubscribe();
435
+ finishStream(res, jsonResponse);
436
+ }
437
+ return;
438
+ }
439
+
440
+ // Handle regular content in streaming events
441
+ if (delta.content !== undefined) {
442
+ if (delta.content === null) {
443
+ // Skip null content chunks
444
+ return;
445
+ }
446
+ chunkTextIntoTokens(delta.content, false, useSingleTokenStream).forEach(token => {
447
+ fillJsonResponse(jsonResponse, token, null);
448
+ sendStreamData(jsonResponse);
449
+ });
450
+
451
+ if (finishReason === "stop") {
452
+ safeUnsubscribe();
453
+ finishStream(res, jsonResponse);
454
+ }
455
+ return;
456
+ }
457
+ }
458
+
227
459
  let content = '';
228
460
  if (messageJson.choices) {
229
461
  const { text, delta } = messageJson.choices[0];
@@ -232,6 +464,13 @@ const processIncomingStream = (requestId, res, jsonResponse, pathway) => {
232
464
  content = messageJson.candidates[0].content.parts[0].text;
233
465
  } else if (messageJson.content) {
234
466
  content = messageJson.content?.[0]?.text || '';
467
+ } else if (messageJson.tool_calls) {
468
+ // Handle tool calls in streaming
469
+ fillJsonResponseWithToolCalls(jsonResponse, messageJson.tool_calls, "tool_calls");
470
+ sendStreamData(jsonResponse);
471
+ safeUnsubscribe();
472
+ finishStream(res, jsonResponse);
473
+ return;
235
474
  } else {
236
475
  content = messageJson;
237
476
  }
@@ -289,7 +528,8 @@ function buildRestEndpoints(pathways, app, server, config) {
289
528
  }
290
529
  } else {
291
530
  app.post(`/rest/${name}`, async (req, res) => {
292
- const resultText = await processRestRequest(server, req, pathway, name);
531
+ const pathwayResponse = await processRestRequest(server, req, pathway, name);
532
+ const { resultText } = extractResponseData(pathwayResponse);
293
533
  res.send(resultText);
294
534
  });
295
535
  }
@@ -298,29 +538,21 @@ function buildRestEndpoints(pathways, app, server, config) {
298
538
  // Create OpenAI compatible endpoints
299
539
  app.post('/v1/completions', async (req, res) => {
300
540
  const modelName = req.body.model || 'gpt-3.5-turbo';
301
- let pathwayName;
302
-
303
- if (modelName.startsWith('ollama-')) {
304
- pathwayName = 'sys_ollama_completion';
305
- req.body.ollamaModel = modelName.replace('ollama-', '');
306
- } else {
307
- pathwayName = openAICompletionModels[modelName] || openAICompletionModels['*'];
308
- }
541
+ const { pathwayName, isOllama } = resolveModelName(modelName, openAIChatModels, openAICompletionModels, false);
309
542
 
310
543
  if (!pathwayName) {
311
- res.status(404).json({
312
- error: `Model ${modelName} not found.`,
313
- });
544
+ handleModelNotFound(res, modelName);
314
545
  return;
315
546
  }
316
547
 
317
- const pathway = pathways[pathwayName];
318
-
319
- const parameterMap = {
320
- text: 'prompt'
321
- };
548
+ if (isOllama) {
549
+ req.body.ollamaModel = modelName.replace('ollama-', '');
550
+ }
322
551
 
323
- const resultText = await processRestRequest(server, req, pathway, pathwayName, parameterMap);
552
+ const pathway = pathways[pathwayName];
553
+ const parameterMap = { text: 'prompt' };
554
+ const pathwayResponse = await processRestRequest(server, req, pathway, pathwayName, parameterMap);
555
+ const { resultText } = extractResponseData(pathwayResponse);
324
556
 
325
557
  const jsonResponse = {
326
558
  id: `cmpl`,
@@ -341,50 +573,46 @@ function buildRestEndpoints(pathways, app, server, config) {
341
573
  if (Boolean(req.body.stream)) {
342
574
  jsonResponse.id = `cmpl-${resultText}`;
343
575
  jsonResponse.choices[0].finish_reason = null;
344
-
345
576
  processIncomingStream(resultText, res, jsonResponse, pathway);
346
577
  } else {
347
- const requestId = uuidv4();
348
- jsonResponse.id = `cmpl-${requestId}`;
578
+ jsonResponse.id = generateResponseId('cmpl');
349
579
  res.json(jsonResponse);
350
580
  }
351
581
  });
352
582
 
353
583
  app.post('/v1/chat/completions', async (req, res) => {
354
584
  const modelName = req.body.model || 'gpt-3.5-turbo';
355
- let pathwayName;
356
-
357
- if (modelName.startsWith('ollama-')) {
358
- pathwayName = 'sys_ollama_chat';
359
- req.body.ollamaModel = modelName.replace('ollama-', '');
360
- } else {
361
- pathwayName = openAIChatModels[modelName] || openAIChatModels['*'];
362
- }
585
+ const { pathwayName, isOllama } = resolveModelName(modelName, openAIChatModels, openAICompletionModels, true);
363
586
 
364
587
  if (!pathwayName) {
365
- res.status(404).json({
366
- error: `Model ${modelName} not found.`,
367
- });
588
+ handleModelNotFound(res, modelName);
368
589
  return;
369
590
  }
370
591
 
371
- const pathway = pathways[pathwayName];
592
+ if (isOllama) {
593
+ req.body.ollamaModel = modelName.replace('ollama-', '');
594
+ }
372
595
 
373
- const resultText = await processRestRequest(server, req, pathway, pathwayName);
596
+ const pathway = pathways[pathwayName];
597
+ const pathwayResponse = await processRestRequest(server, req, pathway, pathwayName);
598
+ const { resultText, resultData } = extractResponseData(pathwayResponse);
599
+ const { messageContent, toolCalls, functionCall, finishReason } = parseToolCalls(resultData, resultText);
374
600
 
375
601
  const jsonResponse = {
376
602
  id: `chatcmpl`,
377
- object: "chat.completion",
603
+ object: Boolean(req.body.stream) ? "chat.completion.chunk" : "chat.completion",
378
604
  created: Date.now(),
379
605
  model: req.body.model,
380
606
  choices: [
381
607
  {
382
608
  message: {
383
609
  role: "assistant",
384
- content: resultText
610
+ content: messageContent,
611
+ ...(toolCalls && { tool_calls: toolCalls }),
612
+ ...(functionCall && { function_call: functionCall })
385
613
  },
386
614
  index: 0,
387
- finish_reason: "stop"
615
+ finish_reason: finishReason
388
616
  }
389
617
  ],
390
618
  };
@@ -392,23 +620,12 @@ function buildRestEndpoints(pathways, app, server, config) {
392
620
  // eslint-disable-next-line no-extra-boolean-cast
393
621
  if (Boolean(req.body.stream)) {
394
622
  jsonResponse.id = `chatcmpl-${resultText}`;
395
- jsonResponse.choices[0] = {
396
- delta: {
397
- role: "assistant",
398
- content: resultText
399
- },
400
- finish_reason: null
401
- }
402
- jsonResponse.object = "chat.completion.chunk";
403
-
623
+ jsonResponse.choices[0].finish_reason = null;
404
624
  processIncomingStream(resultText, res, jsonResponse, pathway);
405
625
  } else {
406
- const requestId = uuidv4();
407
- jsonResponse.id = `chatcmpl-${requestId}`;
408
-
626
+ jsonResponse.id = generateResponseId('chatcmpl');
409
627
  res.json(jsonResponse);
410
628
  }
411
-
412
629
  });
413
630
 
414
631
  app.get('/v1/models', async (req, res) => {
package/server/typeDef.js CHANGED
@@ -39,6 +39,7 @@ const getPathwayTypeDef = (name, returnType) => {
39
39
  return `type ${name} {
40
40
  debug: String
41
41
  result: ${returnType}
42
+ resultData: String
42
43
  previousResult: String
43
44
  warnings: [String]
44
45
  errors: [String]
@@ -1,4 +1,4 @@
1
- import { Prompt } from '../server/prompt.js';
1
+ import { Prompt } from '../../server/prompt.js';
2
2
 
3
3
  export const mockConfig = {
4
4
  get: (key) => {
@@ -92,4 +92,7 @@ export const mockConfig = {
92
92
  }),
93
93
  };
94
94
 
95
- export const mockModelEndpoints = { testModel: { name: 'testModel', url: 'https://api.example.com/testModel', type: 'OPENAI-COMPLETION' }};
95
+ export const mockModelEndpoints = {
96
+ testModel: { name: 'testModel', url: 'https://api.example.com/testModel', type: 'OPENAI-COMPLETION' },
97
+ anotherModel: { name: 'anotherModel', url: 'https://api.example.com/anotherModel', type: 'OPENAI-CHAT' }
98
+ };
@@ -1,7 +1,7 @@
1
1
  import 'dotenv/config'
2
2
  import { ApolloServer } from '@apollo/server';
3
- import { config } from '../config.js';
4
- import typeDefsresolversFactory from '../index.js';
3
+ import { config } from '../../config.js';
4
+ import typeDefsresolversFactory from '../../index.js';
5
5
 
6
6
  let typeDefs;
7
7
  let resolvers;
@@ -0,0 +1,23 @@
1
+ import { connectToSSEEndpoint } from './sseClient.js';
2
+
3
+ export async function collectSSEChunks(baseUrl, endpoint, payload) {
4
+ const chunks = [];
5
+ await connectToSSEEndpoint(baseUrl, endpoint, payload, (chunk) => {
6
+ chunks.push(chunk);
7
+ });
8
+ return chunks;
9
+ }
10
+
11
+ export function assertOAIChatChunkBasics(t, chunk) {
12
+ t.truthy(chunk.id);
13
+ t.is(chunk.object, 'chat.completion.chunk');
14
+ t.truthy(chunk.choices && chunk.choices[0]);
15
+ const choice = chunk.choices[0];
16
+ t.true('delta' in choice);
17
+ }
18
+
19
+ export function assertAnyContentDelta(chunks) {
20
+ return chunks.some(c => c?.choices?.[0]?.delta?.content);
21
+ }
22
+
23
+
@@ -0,0 +1,73 @@
1
+ import axios from 'axios';
2
+
3
+ // Connects to an SSE endpoint and resolves when [DONE] is received.
4
+ // Calls onEvent for each parsed SSE message JSON.
5
+ export async function connectToSSEEndpoint(baseUrl, endpoint, payload, onEvent) {
6
+ return new Promise(async (resolve, reject) => {
7
+ let sawDone = false;
8
+ const timeout = setTimeout(() => {
9
+ reject(new Error('SSE timeout waiting for [DONE]'));
10
+ }, 8000); // 8 second timeout
11
+
12
+ try {
13
+ const instance = axios.create({
14
+ baseURL: baseUrl,
15
+ responseType: 'stream',
16
+ });
17
+
18
+ const response = await instance.post(endpoint, payload);
19
+ const responseData = response.data;
20
+
21
+ const incomingMessage = Array.isArray(responseData) && responseData.length > 0
22
+ ? responseData[0]
23
+ : responseData;
24
+
25
+ let eventCount = 0;
26
+
27
+ incomingMessage.on('data', data => {
28
+ const events = data.toString().split('\n');
29
+
30
+ events.forEach(event => {
31
+ if (event.trim() === '') return;
32
+
33
+ eventCount++;
34
+
35
+ if (event.trim() === 'data: [DONE]') {
36
+ sawDone = true;
37
+ clearTimeout(timeout);
38
+ resolve();
39
+ return;
40
+ }
41
+
42
+ const message = event.replace(/^data: /, '');
43
+ try {
44
+ const messageJson = JSON.parse(message);
45
+ onEvent && onEvent(messageJson);
46
+ } catch (_err) {
47
+ // ignore lines that are not JSON
48
+ }
49
+ });
50
+ });
51
+
52
+ // If the underlying stream ends without a [DONE], treat as failure
53
+ incomingMessage.on('end', () => {
54
+ if (!sawDone) {
55
+ clearTimeout(timeout);
56
+ reject(new Error('SSE stream ended without [DONE]'));
57
+ }
58
+ });
59
+
60
+ incomingMessage.on('close', () => {
61
+ if (!sawDone) {
62
+ clearTimeout(timeout);
63
+ reject(new Error('SSE stream closed without [DONE]'));
64
+ }
65
+ });
66
+ } catch (error) {
67
+ clearTimeout(timeout);
68
+ reject(error);
69
+ }
70
+ });
71
+ }
72
+
73
+
@@ -0,0 +1,11 @@
1
+ export function assertOAIStyleDeltaMessage(t, message) {
2
+ const progress = message?.data?.requestProgress;
3
+ t.truthy(progress);
4
+ if (progress.data) {
5
+ // Should be string of serialized JSON or plain text
6
+ const text = JSON.parse(progress.data);
7
+ t.true(typeof text === 'string' || typeof text === 'object');
8
+ }
9
+ }
10
+
11
+