@aj-archipelago/cortex 1.3.62 → 1.3.64

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 (215) 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/shared/sys_memory_helpers.js +1 -1
  91. package/pathways/system/entity/memory/sys_memory_lookup_required.js +1 -0
  92. package/pathways/system/entity/memory/sys_memory_manager.js +1 -1
  93. package/pathways/system/entity/memory/sys_memory_required.js +1 -0
  94. package/pathways/system/entity/memory/sys_search_memory.js +1 -0
  95. package/pathways/system/entity/sys_entity_agent.js +72 -7
  96. package/pathways/system/entity/sys_generator_quick.js +1 -0
  97. package/pathways/system/entity/sys_generator_results.js +1 -1
  98. package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +26 -0
  99. package/pathways/system/entity/tools/sys_tool_google_search.js +141 -0
  100. package/pathways/system/entity/tools/sys_tool_grok_x_search.js +237 -0
  101. package/pathways/system/entity/tools/sys_tool_image.js +1 -1
  102. package/pathways/system/rest_streaming/sys_claude_37_sonnet.js +21 -0
  103. package/pathways/system/rest_streaming/sys_claude_41_opus.js +21 -0
  104. package/pathways/system/rest_streaming/sys_claude_4_sonnet.js +21 -0
  105. package/pathways/system/rest_streaming/sys_google_gemini_25_flash.js +25 -0
  106. package/pathways/system/rest_streaming/{sys_google_gemini_chat.js → sys_google_gemini_25_pro.js} +6 -4
  107. package/pathways/system/rest_streaming/sys_grok_4.js +23 -0
  108. package/pathways/system/rest_streaming/sys_grok_4_fast_non_reasoning.js +23 -0
  109. package/pathways/system/rest_streaming/sys_grok_4_fast_reasoning.js +23 -0
  110. package/pathways/system/rest_streaming/sys_openai_chat.js +3 -0
  111. package/pathways/system/rest_streaming/sys_openai_chat_gpt41.js +22 -0
  112. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_mini.js +21 -0
  113. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_nano.js +21 -0
  114. package/pathways/system/rest_streaming/{sys_claude_35_sonnet.js → sys_openai_chat_gpt4_omni.js} +6 -4
  115. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_omni_mini.js +21 -0
  116. package/pathways/system/rest_streaming/{sys_claude_3_haiku.js → sys_openai_chat_gpt5.js} +7 -5
  117. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_chat.js +21 -0
  118. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_mini.js +21 -0
  119. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_nano.js +21 -0
  120. package/pathways/system/rest_streaming/{sys_openai_chat_o1.js → sys_openai_chat_o3.js} +6 -3
  121. package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +3 -0
  122. package/pathways/system/workspaces/run_workspace_prompt.js +99 -0
  123. package/pathways/vision.js +1 -1
  124. package/server/graphql.js +1 -1
  125. package/server/modelExecutor.js +8 -0
  126. package/server/pathwayResolver.js +166 -16
  127. package/server/pathwayResponseParser.js +16 -8
  128. package/server/plugins/azureFoundryAgentsPlugin.js +1 -1
  129. package/server/plugins/claude3VertexPlugin.js +193 -45
  130. package/server/plugins/gemini15ChatPlugin.js +21 -0
  131. package/server/plugins/gemini15VisionPlugin.js +362 -0
  132. package/server/plugins/googleCsePlugin.js +94 -0
  133. package/server/plugins/grokVisionPlugin.js +365 -0
  134. package/server/plugins/modelPlugin.js +3 -1
  135. package/server/plugins/openAiChatPlugin.js +106 -13
  136. package/server/plugins/openAiVisionPlugin.js +45 -31
  137. package/server/resolver.js +28 -4
  138. package/server/rest.js +270 -53
  139. package/server/typeDef.js +1 -0
  140. package/tests/{mocks.js → helpers/mocks.js} +5 -2
  141. package/tests/{server.js → helpers/server.js} +2 -2
  142. package/tests/helpers/sseAssert.js +23 -0
  143. package/tests/helpers/sseClient.js +73 -0
  144. package/tests/helpers/subscriptionAssert.js +11 -0
  145. package/tests/helpers/subscriptions.js +113 -0
  146. package/tests/{sublong.srt → integration/features/translate/sublong.srt} +4543 -4543
  147. package/tests/integration/features/translate/translate_chunking_stream.test.js +100 -0
  148. package/tests/{translate_srt.test.js → integration/features/translate/translate_srt.test.js} +2 -2
  149. package/tests/integration/graphql/async/stream/agentic.test.js +477 -0
  150. package/tests/integration/graphql/async/stream/subscription_streaming.test.js +62 -0
  151. package/tests/integration/graphql/async/stream/sys_entity_start_streaming.test.js +71 -0
  152. package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +56 -0
  153. package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +66 -0
  154. package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +56 -0
  155. package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +72 -0
  156. package/tests/integration/graphql/features/google/sysToolGoogleSearch.test.js +96 -0
  157. package/tests/integration/graphql/features/grok/grok.test.js +688 -0
  158. package/tests/integration/graphql/features/grok/grok_x_search_tool.test.js +354 -0
  159. package/tests/{main.test.js → integration/graphql/features/main.test.js} +1 -1
  160. package/tests/{call_tools.test.js → integration/graphql/features/tools/call_tools.test.js} +2 -2
  161. package/tests/{vision.test.js → integration/graphql/features/vision/vision.test.js} +1 -1
  162. package/tests/integration/graphql/subscriptions/connection.test.js +26 -0
  163. package/tests/{openai_api.test.js → integration/rest/oai/openai_api.test.js} +63 -238
  164. package/tests/integration/rest/oai/tool_calling_api.test.js +343 -0
  165. package/tests/integration/rest/oai/tool_calling_streaming.test.js +85 -0
  166. package/tests/integration/rest/vendors/claude_streaming.test.js +47 -0
  167. package/tests/integration/rest/vendors/claude_tool_calling_streaming.test.js +75 -0
  168. package/tests/integration/rest/vendors/gemini_streaming.test.js +47 -0
  169. package/tests/integration/rest/vendors/gemini_tool_calling_streaming.test.js +75 -0
  170. package/tests/integration/rest/vendors/grok_streaming.test.js +55 -0
  171. package/tests/integration/rest/vendors/grok_tool_calling_streaming.test.js +75 -0
  172. package/tests/{azureAuthTokenHelper.test.js → unit/core/azureAuthTokenHelper.test.js} +1 -1
  173. package/tests/{chunkfunction.test.js → unit/core/chunkfunction.test.js} +2 -2
  174. package/tests/{config.test.js → unit/core/config.test.js} +3 -3
  175. package/tests/{encodeCache.test.js → unit/core/encodeCache.test.js} +1 -1
  176. package/tests/{fastLruCache.test.js → unit/core/fastLruCache.test.js} +1 -1
  177. package/tests/{handleBars.test.js → unit/core/handleBars.test.js} +1 -1
  178. package/tests/{memoryfunction.test.js → unit/core/memoryfunction.test.js} +2 -2
  179. package/tests/unit/core/mergeResolver.test.js +952 -0
  180. package/tests/{parser.test.js → unit/core/parser.test.js} +3 -3
  181. package/tests/unit/core/pathwayResolver.test.js +187 -0
  182. package/tests/{requestMonitor.test.js → unit/core/requestMonitor.test.js} +1 -1
  183. package/tests/{requestMonitorDurationEstimator.test.js → unit/core/requestMonitorDurationEstimator.test.js} +1 -1
  184. package/tests/{truncateMessages.test.js → unit/core/truncateMessages.test.js} +3 -3
  185. package/tests/{util.test.js → unit/core/util.test.js} +1 -1
  186. package/tests/{apptekTranslatePlugin.test.js → unit/plugins/apptekTranslatePlugin.test.js} +3 -3
  187. package/tests/{azureFoundryAgents.test.js → unit/plugins/azureFoundryAgents.test.js} +136 -1
  188. package/tests/{claude3VertexPlugin.test.js → unit/plugins/claude3VertexPlugin.test.js} +32 -10
  189. package/tests/{claude3VertexToolConversion.test.js → unit/plugins/claude3VertexToolConversion.test.js} +3 -3
  190. package/tests/unit/plugins/googleCsePlugin.test.js +111 -0
  191. package/tests/unit/plugins/grokVisionPlugin.test.js +1392 -0
  192. package/tests/{modelPlugin.test.js → unit/plugins/modelPlugin.test.js} +3 -3
  193. package/tests/{multimodal_conversion.test.js → unit/plugins/multimodal_conversion.test.js} +4 -4
  194. package/tests/{openAiChatPlugin.test.js → unit/plugins/openAiChatPlugin.test.js} +13 -4
  195. package/tests/{openAiToolPlugin.test.js → unit/plugins/openAiToolPlugin.test.js} +35 -27
  196. package/tests/{tokenHandlingTests.test.js → unit/plugins/tokenHandlingTests.test.js} +5 -5
  197. package/tests/unit/plugins/toolCallBufferFiltering.test.js +297 -0
  198. package/tests/{translate_apptek.test.js → unit/plugins/translate_apptek.test.js} +3 -3
  199. package/tests/{streaming.test.js → unit/plugins.streaming/plugin_stream_events.test.js} +19 -58
  200. package/helper-apps/mogrt-handler/tests/test-files/test.gif +0 -1
  201. package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +0 -1
  202. package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +0 -1
  203. package/pathways/system/rest_streaming/sys_openai_chat_gpt4.js +0 -19
  204. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_32.js +0 -19
  205. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_turbo.js +0 -19
  206. package/pathways/system/workspaces/run_claude35_sonnet.js +0 -21
  207. package/pathways/system/workspaces/run_claude3_haiku.js +0 -20
  208. package/pathways/system/workspaces/run_gpt35turbo.js +0 -20
  209. package/pathways/system/workspaces/run_gpt4.js +0 -20
  210. package/pathways/system/workspaces/run_gpt4_32.js +0 -20
  211. package/tests/agentic.test.js +0 -256
  212. package/tests/pathwayResolver.test.js +0 -78
  213. package/tests/subscription.test.js +0 -387
  214. /package/tests/{subchunk.srt → integration/features/translate/subchunk.srt} +0 -0
  215. /package/tests/{subhorizontal.srt → integration/features/translate/subhorizontal.srt} +0 -0
@@ -2,6 +2,7 @@ import OpenAIChatPlugin from './openAiChatPlugin.js';
2
2
  import logger from '../../lib/logger.js';
3
3
  import { requestState } from '../requestState.js';
4
4
  import { addCitationsToResolver } from '../../lib/pathwayTools.js';
5
+ import CortexResponse from '../../lib/cortexResponse.js';
5
6
  function safeJsonParse(content) {
6
7
  try {
7
8
  const parsedContent = JSON.parse(content);
@@ -113,16 +114,14 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
113
114
  }
114
115
  if (stream) {
115
116
  logger.info(`[response received as an SSE stream]`);
116
- } else {
117
- const parsedResponse = this.parseResponse(responseData);
118
-
119
- if (typeof parsedResponse === 'string') {
120
- const { length, units } = this.getLength(parsedResponse);
117
+ } else {
118
+ if (typeof responseData === 'string') {
119
+ const { length, units } = this.getLength(responseData);
121
120
  logger.info(`[response received containing ${length} ${units}]`);
122
- logger.verbose(`${this.shortenContent(parsedResponse)}`);
121
+ logger.verbose(`${this.shortenContent(responseData)}`);
123
122
  } else {
124
123
  logger.info(`[response received containing object]`);
125
- logger.verbose(`${JSON.stringify(parsedResponse)}`);
124
+ logger.verbose(`${JSON.stringify(responseData)}`);
126
125
  }
127
126
  }
128
127
 
@@ -135,15 +134,6 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
135
134
 
136
135
  requestParameters.messages = await this.tryParseMessages(requestParameters.messages);
137
136
 
138
- // Add tools support if provided in parameters
139
- if (parameters.tools) {
140
- requestParameters.tools = parameters.tools;
141
- }
142
-
143
- if (parameters.tool_choice) {
144
- requestParameters.tool_choice = parameters.tool_choice;
145
- }
146
-
147
137
  const modelMaxReturnTokens = this.getModelMaxReturnTokens();
148
138
  const maxTokensPrompt = this.promptParameters.max_tokens;
149
139
  const maxTokensModel = this.getModelMaxTokenLength() * (1 - this.getPromptTokenRatio());
@@ -165,7 +155,7 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
165
155
  ...(cortexRequest.data || {}),
166
156
  ...requestParameters,
167
157
  };
168
- cortexRequest.params = {}; // query params
158
+ cortexRequest.params = {};
169
159
  cortexRequest.stream = stream;
170
160
 
171
161
  return this.executeRequest(cortexRequest);
@@ -179,24 +169,30 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
179
169
  return data;
180
170
  }
181
171
 
182
- // if we got a choices array back with more than one choice, return the whole array
183
- if (choices.length > 1) {
184
- return choices;
185
- }
186
-
187
172
  const choice = choices[0];
188
173
  const message = choice.message;
174
+ if (!message) {
175
+ return null;
176
+ }
177
+
178
+ // Create standardized CortexResponse object
179
+ const cortexResponse = new CortexResponse({
180
+ output_text: message.content || "",
181
+ finishReason: choice.finish_reason || 'stop',
182
+ usage: data.usage || null,
183
+ metadata: {
184
+ model: this.modelName
185
+ }
186
+ });
189
187
 
190
- // Handle tool calls in the response
188
+ // Handle tool calls
191
189
  if (message.tool_calls) {
192
- return {
193
- role: message.role,
194
- content: message.content || "",
195
- tool_calls: message.tool_calls
196
- };
190
+ cortexResponse.toolCalls = message.tool_calls;
191
+ } else if (message.function_call) {
192
+ cortexResponse.functionCall = message.function_call;
197
193
  }
198
194
 
199
- return message.content || "";
195
+ return cortexResponse;
200
196
  }
201
197
 
202
198
  processStreamEvent(event, requestProgress) {
@@ -210,7 +206,6 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
210
206
  let parsedMessage;
211
207
  try {
212
208
  parsedMessage = JSON.parse(event.data);
213
- requestProgress.data = event.data;
214
209
  } catch (error) {
215
210
  // Clear buffers on error
216
211
  this.toolCallsBuffer = [];
@@ -228,6 +223,23 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
228
223
  }
229
224
 
230
225
  const delta = parsedMessage?.choices?.[0]?.delta;
226
+
227
+ // Check if this is an empty/idle event that we should skip
228
+ const isEmptyEvent = !delta ||
229
+ (Object.keys(delta).length === 0) ||
230
+ (Object.keys(delta).length === 1 && delta.content === '') ||
231
+ (Object.keys(delta).length === 1 && delta.tool_calls && delta.tool_calls.length === 0);
232
+
233
+ // Skip publishing empty events unless they have a finish_reason
234
+ const hasFinishReason = parsedMessage?.choices?.[0]?.finish_reason;
235
+
236
+ if (isEmptyEvent && !hasFinishReason) {
237
+ // Return requestProgress without setting data to prevent publishing
238
+ return requestProgress;
239
+ }
240
+
241
+ // Set the data for non-empty events or events with finish_reason
242
+ requestProgress.data = event.data;
231
243
 
232
244
  // Accumulate content
233
245
  if (delta?.content) {
@@ -268,10 +280,12 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
268
280
  case 'tool_calls':
269
281
  // Process complete tool calls when we get the finish reason
270
282
  if (this.pathwayToolCallback && this.toolCallsBuffer.length > 0 && pathwayResolver) {
283
+ // Filter out undefined elements from the tool calls buffer
284
+ const validToolCalls = this.toolCallsBuffer.filter(tc => tc && tc.function && tc.function.name);
271
285
  const toolMessage = {
272
286
  role: 'assistant',
273
287
  content: delta?.content || '',
274
- tool_calls: this.toolCallsBuffer,
288
+ tool_calls: validToolCalls,
275
289
  };
276
290
  this.pathwayToolCallback(pathwayResolver?.args, toolMessage, pathwayResolver);
277
291
  }
@@ -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
+ };