@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
@@ -0,0 +1,952 @@
1
+ // mergeResolver.test.js
2
+ // Comprehensive tests for mergeResolver and mergeResultData methods
3
+
4
+ import test from 'ava';
5
+ import { PathwayResolver } from '../../../server/pathwayResolver.js';
6
+ import CortexResponse from '../../../lib/cortexResponse.js';
7
+ import { mockConfig, mockPathwayString, mockModelEndpoints } from '../../helpers/mocks.js';
8
+
9
+ const mockPathway = mockPathwayString;
10
+ mockPathway.useInputChunking = false;
11
+ mockPathway.prompt = 'What is AI?';
12
+
13
+ const mockArgs = {
14
+ text: 'Artificial intelligence',
15
+ };
16
+
17
+ test.beforeEach((t) => {
18
+ t.context.pathwayResolver = new PathwayResolver({
19
+ config: mockConfig,
20
+ pathway: mockPathway,
21
+ args: mockArgs,
22
+ endpoints: mockModelEndpoints,
23
+ });
24
+ });
25
+
26
+ // ============================================================================
27
+ // mergeResolver Tests
28
+ // ============================================================================
29
+
30
+ test('mergeResolver merges basic resolver properties correctly', (t) => {
31
+ const resolver1 = t.context.pathwayResolver;
32
+ const resolver2 = new PathwayResolver({
33
+ config: mockConfig,
34
+ pathway: mockPathway,
35
+ args: mockArgs,
36
+ endpoints: mockModelEndpoints,
37
+ });
38
+
39
+ // Set up initial data
40
+ resolver1.previousResult = 'initial result';
41
+ resolver1.warnings = ['warning1'];
42
+ resolver1.errors = ['error1'];
43
+ resolver1.pathwayResultData = { citations: ['cite1'], usage: { tokens: 100 } };
44
+
45
+ resolver2.previousResult = 'new result';
46
+ resolver2.warnings = ['warning2'];
47
+ resolver2.errors = ['error2'];
48
+ resolver2.pathwayResultData = { citations: ['cite2'], usage: { tokens: 200 } };
49
+
50
+ resolver1.mergeResolver(resolver2);
51
+
52
+ t.is(resolver1.previousResult, 'new result'); // Should use other resolver's result
53
+ t.deepEqual(resolver1.warnings, ['warning1', 'warning2']);
54
+ t.deepEqual(resolver1.errors, ['error1', 'error2']);
55
+ t.deepEqual(resolver1.pathwayResultData.citations, ['cite1', 'cite2']);
56
+ });
57
+
58
+ test('mergeResolver handles null/undefined otherResolver gracefully', (t) => {
59
+ const resolver = t.context.pathwayResolver;
60
+ const originalWarnings = ['warning1'];
61
+ const originalErrors = ['error1'];
62
+ const originalResultData = { citations: ['cite1'] };
63
+
64
+ resolver.warnings = originalWarnings;
65
+ resolver.errors = originalErrors;
66
+ resolver.pathwayResultData = originalResultData;
67
+
68
+ // Test with null
69
+ resolver.mergeResolver(null);
70
+ t.deepEqual(resolver.warnings, originalWarnings);
71
+ t.deepEqual(resolver.errors, originalErrors);
72
+ t.deepEqual(resolver.pathwayResultData, originalResultData);
73
+
74
+ // Test with undefined
75
+ resolver.mergeResolver(undefined);
76
+ t.deepEqual(resolver.warnings, originalWarnings);
77
+ t.deepEqual(resolver.errors, originalErrors);
78
+ t.deepEqual(resolver.pathwayResultData, originalResultData);
79
+ });
80
+
81
+ test('mergeResolver preserves original previousResult when other has none', (t) => {
82
+ const resolver1 = t.context.pathwayResolver;
83
+ const resolver2 = new PathwayResolver({
84
+ config: mockConfig,
85
+ pathway: mockPathway,
86
+ args: mockArgs,
87
+ endpoints: mockModelEndpoints,
88
+ });
89
+
90
+ resolver1.previousResult = 'original result';
91
+ resolver2.previousResult = null;
92
+
93
+ resolver1.mergeResolver(resolver2);
94
+ t.is(resolver1.previousResult, 'original result');
95
+ });
96
+
97
+ test('mergeResolver uses other previousResult when original is null', (t) => {
98
+ const resolver1 = t.context.pathwayResolver;
99
+ const resolver2 = new PathwayResolver({
100
+ config: mockConfig,
101
+ pathway: mockPathway,
102
+ args: mockArgs,
103
+ endpoints: mockModelEndpoints,
104
+ });
105
+
106
+ resolver1.previousResult = null;
107
+ resolver2.previousResult = 'new result';
108
+
109
+ resolver1.mergeResolver(resolver2);
110
+ t.is(resolver1.previousResult, 'new result');
111
+ });
112
+
113
+ test('mergeResolver handles empty arrays correctly', (t) => {
114
+ const resolver1 = t.context.pathwayResolver;
115
+ const resolver2 = new PathwayResolver({
116
+ config: mockConfig,
117
+ pathway: mockPathway,
118
+ args: mockArgs,
119
+ endpoints: mockModelEndpoints,
120
+ });
121
+
122
+ resolver1.warnings = [];
123
+ resolver1.errors = [];
124
+ resolver2.warnings = [];
125
+ resolver2.errors = [];
126
+
127
+ resolver1.mergeResolver(resolver2);
128
+ t.deepEqual(resolver1.warnings, []);
129
+ t.deepEqual(resolver1.errors, []);
130
+ });
131
+
132
+ // ============================================================================
133
+ // mergeResultData Tests - Basic Object Merging
134
+ // ============================================================================
135
+
136
+ test('mergeResultData returns current data when newData is null/undefined', (t) => {
137
+ const resolver = t.context.pathwayResolver;
138
+ const originalData = { citations: ['cite1'], usage: { tokens: 100 } };
139
+ resolver.pathwayResultData = originalData;
140
+
141
+ const result1 = resolver.mergeResultData(null);
142
+ const result2 = resolver.mergeResultData(undefined);
143
+
144
+ t.deepEqual(result1, originalData);
145
+ t.deepEqual(result2, originalData);
146
+ });
147
+
148
+ test('mergeResultData merges simple objects correctly', (t) => {
149
+ const resolver = t.context.pathwayResolver;
150
+ resolver.pathwayResultData = {
151
+ citations: ['cite1'],
152
+ usage: { tokens: 100 },
153
+ metadata: { source: 'test1' }
154
+ };
155
+
156
+ const newData = {
157
+ citations: ['cite2'],
158
+ usage: { tokens: 200 },
159
+ metadata: { source: 'test2' }
160
+ };
161
+
162
+ const result = resolver.mergeResultData(newData);
163
+
164
+ t.deepEqual(result.citations, ['cite1', 'cite2']);
165
+ t.deepEqual(result.usage, [{ tokens: 200 }, { tokens: 100 }]); // Should be converted to array
166
+ t.deepEqual(result.metadata, { source: 'test2' });
167
+ });
168
+
169
+ test('mergeResultData handles empty current data', (t) => {
170
+ const resolver = t.context.pathwayResolver;
171
+ resolver.pathwayResultData = null;
172
+
173
+ const newData = {
174
+ citations: ['cite1'],
175
+ usage: { tokens: 100 },
176
+ finishReason: 'stop'
177
+ };
178
+
179
+ const result = resolver.mergeResultData(newData);
180
+
181
+ t.deepEqual(result.citations, newData.citations);
182
+ t.deepEqual(result.finishReason, newData.finishReason);
183
+ t.deepEqual(result.usage, [{ tokens: 100 }]); // Should be converted to array
184
+ });
185
+
186
+ // ============================================================================
187
+ // mergeResultData Tests - CortexResponse Handling
188
+ // ============================================================================
189
+
190
+ test('mergeResultData correctly handles CortexResponse objects', (t) => {
191
+ const resolver = t.context.pathwayResolver;
192
+ resolver.pathwayResultData = {
193
+ citations: ['cite1'],
194
+ usage: { tokens: 100 }
195
+ };
196
+
197
+ const cortexResponse = new CortexResponse({
198
+ output_text: 'Test response',
199
+ citations: ['cite2'],
200
+ toolCalls: [{ name: 'test_tool', args: {} }],
201
+ usage: { tokens: 200 },
202
+ finishReason: 'tool_calls'
203
+ });
204
+
205
+ const result = resolver.mergeResultData(cortexResponse);
206
+
207
+ t.deepEqual(result.citations, ['cite1', 'cite2']);
208
+ t.deepEqual(result.toolCalls, [{ name: 'test_tool', args: {} }]);
209
+ t.deepEqual(result.usage, [{ tokens: 200 }, { tokens: 100 }]); // Should be converted to array
210
+ t.is(result.finishReason, 'tool_calls');
211
+ });
212
+
213
+ test('mergeResultData handles CortexResponse with empty arrays', (t) => {
214
+ const resolver = t.context.pathwayResolver;
215
+ resolver.pathwayResultData = {
216
+ citations: ['cite1'],
217
+ toolCalls: [{ name: 'existing_tool', args: {} }]
218
+ };
219
+
220
+ const cortexResponse = new CortexResponse({
221
+ output_text: 'Test response',
222
+ citations: [],
223
+ toolCalls: [],
224
+ usage: { tokens: 100 }
225
+ });
226
+
227
+ const result = resolver.mergeResultData(cortexResponse);
228
+
229
+ t.deepEqual(result.citations, ['cite1']); // Should preserve existing
230
+ t.deepEqual(result.toolCalls, [{ name: 'existing_tool', args: {} }]); // Should preserve existing
231
+ t.deepEqual(result.usage, [{ tokens: 100 }]); // Should be converted to array
232
+ });
233
+
234
+ test('mergeResultData handles CortexResponse with null arrays', (t) => {
235
+ const resolver = t.context.pathwayResolver;
236
+ resolver.pathwayResultData = {
237
+ citations: ['cite1'],
238
+ toolCalls: [{ name: 'existing_tool', args: {} }]
239
+ };
240
+
241
+ const cortexResponse = new CortexResponse({
242
+ output_text: 'Test response',
243
+ citations: null,
244
+ toolCalls: null,
245
+ usage: { tokens: 100 }
246
+ });
247
+
248
+ const result = resolver.mergeResultData(cortexResponse);
249
+
250
+ t.deepEqual(result.citations, ['cite1']); // Should preserve existing
251
+ t.deepEqual(result.toolCalls, [{ name: 'existing_tool', args: {} }]); // Should preserve existing
252
+ t.deepEqual(result.usage, [{ tokens: 100 }]); // Should be converted to array
253
+ });
254
+
255
+ // ============================================================================
256
+ // mergeResultData Tests - Array Field Handling
257
+ // ============================================================================
258
+
259
+ test('mergeResultData concatenates citations arrays correctly', (t) => {
260
+ const resolver = t.context.pathwayResolver;
261
+ resolver.pathwayResultData = {
262
+ citations: ['cite1', 'cite2']
263
+ };
264
+
265
+ const newData = {
266
+ citations: ['cite3', 'cite4']
267
+ };
268
+
269
+ const result = resolver.mergeResultData(newData);
270
+
271
+ t.deepEqual(result.citations, ['cite1', 'cite2', 'cite3', 'cite4']);
272
+ });
273
+
274
+ test('mergeResultData concatenates toolCalls arrays correctly', (t) => {
275
+ const resolver = t.context.pathwayResolver;
276
+ resolver.pathwayResultData = {
277
+ toolCalls: [{ name: 'tool1', args: {} }]
278
+ };
279
+
280
+ const newData = {
281
+ toolCalls: [{ name: 'tool2', args: {} }, { name: 'tool3', args: {} }]
282
+ };
283
+
284
+ const result = resolver.mergeResultData(newData);
285
+
286
+ t.is(result.toolCalls.length, 3);
287
+ t.is(result.toolCalls[0].name, 'tool1');
288
+ t.is(result.toolCalls[1].name, 'tool2');
289
+ t.is(result.toolCalls[2].name, 'tool3');
290
+ });
291
+
292
+ test('mergeResultData handles mixed array and non-array citations', (t) => {
293
+ const resolver = t.context.pathwayResolver;
294
+ resolver.pathwayResultData = {
295
+ citations: ['cite1']
296
+ };
297
+
298
+ const newData = {
299
+ citations: ['cite2'] // Keep as array to match expected behavior
300
+ };
301
+
302
+ const result = resolver.mergeResultData(newData);
303
+
304
+ t.deepEqual(result.citations, ['cite1', 'cite2']);
305
+ });
306
+
307
+ // ============================================================================
308
+ // mergeResultData Tests - Usage and ToolUsed Array Creation
309
+ // ============================================================================
310
+
311
+ test('mergeResultData creates usage array with new value first', (t) => {
312
+ const resolver = t.context.pathwayResolver;
313
+ resolver.pathwayResultData = {
314
+ usage: { tokens: 100, prompt_tokens: 50 }
315
+ };
316
+
317
+ const newData = {
318
+ usage: { tokens: 200, prompt_tokens: 100 }
319
+ };
320
+
321
+ const result = resolver.mergeResultData(newData);
322
+
323
+ t.is(Array.isArray(result.usage), true);
324
+ t.is(result.usage.length, 2);
325
+ t.deepEqual(result.usage[0], { tokens: 200, prompt_tokens: 100 }); // New first
326
+ t.deepEqual(result.usage[1], { tokens: 100, prompt_tokens: 50 }); // Old second
327
+ });
328
+
329
+ test('mergeResultData creates toolUsed array with new value first', (t) => {
330
+ const resolver = t.context.pathwayResolver;
331
+ resolver.pathwayResultData = {
332
+ toolUsed: 'tool1'
333
+ };
334
+
335
+ const newData = {
336
+ toolUsed: 'tool2'
337
+ };
338
+
339
+ const result = resolver.mergeResultData(newData);
340
+
341
+ t.is(Array.isArray(result.toolUsed), true);
342
+ t.is(result.toolUsed.length, 2);
343
+ t.is(result.toolUsed[0], 'tool2'); // New first
344
+ t.is(result.toolUsed[1], 'tool1'); // Old second
345
+ });
346
+
347
+ test('mergeResultData handles existing usage arrays correctly', (t) => {
348
+ const resolver = t.context.pathwayResolver;
349
+ resolver.pathwayResultData = {
350
+ usage: [{ tokens: 100 }, { tokens: 150 }]
351
+ };
352
+
353
+ const newData = {
354
+ usage: [{ tokens: 200 }, { tokens: 250 }]
355
+ };
356
+
357
+ const result = resolver.mergeResultData(newData);
358
+
359
+ t.is(Array.isArray(result.usage), true);
360
+ t.is(result.usage.length, 4);
361
+ t.deepEqual(result.usage[0], { tokens: 200 }); // New first
362
+ t.deepEqual(result.usage[1], { tokens: 250 }); // New second
363
+ t.deepEqual(result.usage[2], { tokens: 100 }); // Old first
364
+ t.deepEqual(result.usage[3], { tokens: 150 }); // Old second
365
+ });
366
+
367
+ test('mergeResultData handles null usage values correctly', (t) => {
368
+ const resolver = t.context.pathwayResolver;
369
+ resolver.pathwayResultData = {
370
+ usage: null
371
+ };
372
+
373
+ const newData = {
374
+ usage: { tokens: 200 }
375
+ };
376
+
377
+ const result = resolver.mergeResultData(newData);
378
+
379
+ t.is(Array.isArray(result.usage), true);
380
+ t.is(result.usage.length, 1);
381
+ t.deepEqual(result.usage[0], { tokens: 200 });
382
+ });
383
+
384
+ test('mergeResultData handles both null usage values', (t) => {
385
+ const resolver = t.context.pathwayResolver;
386
+ resolver.pathwayResultData = {
387
+ usage: null
388
+ };
389
+
390
+ const newData = {
391
+ usage: null
392
+ };
393
+
394
+ const result = resolver.mergeResultData(newData);
395
+
396
+ t.is(result.usage, null);
397
+ });
398
+
399
+ // ============================================================================
400
+ // Integration Tests - Tool Interface Scenarios
401
+ // ============================================================================
402
+
403
+ test('mergeResolver integrates with tool interface data correctly', (t) => {
404
+ const resolver1 = t.context.pathwayResolver;
405
+ const resolver2 = new PathwayResolver({
406
+ config: mockConfig,
407
+ pathway: mockPathway,
408
+ args: mockArgs,
409
+ endpoints: mockModelEndpoints,
410
+ });
411
+
412
+ // Simulate tool interface data
413
+ resolver1.pathwayResultData = {
414
+ citations: ['source1'],
415
+ toolCalls: [{ name: 'search_tool', args: { query: 'test' } }],
416
+ usage: { tokens: 100 },
417
+ toolUsed: 'search_tool'
418
+ };
419
+
420
+ resolver2.pathwayResultData = {
421
+ citations: ['source2'],
422
+ toolCalls: [{ name: 'analyze_tool', args: { data: 'test' } }],
423
+ usage: { tokens: 150 },
424
+ toolUsed: 'analyze_tool'
425
+ };
426
+
427
+ resolver1.mergeResolver(resolver2);
428
+
429
+ const result = resolver1.pathwayResultData;
430
+
431
+ t.deepEqual(result.citations, ['source1', 'source2']);
432
+ t.is(result.toolCalls.length, 2);
433
+ t.is(result.toolCalls[0].name, 'search_tool');
434
+ t.is(result.toolCalls[1].name, 'analyze_tool');
435
+ t.is(Array.isArray(result.usage), true);
436
+ t.is(result.usage.length, 2);
437
+ t.is(Array.isArray(result.toolUsed), true);
438
+ t.is(result.toolUsed.length, 2);
439
+ });
440
+
441
+ test('mergeResultData handles complex tool interface scenarios', (t) => {
442
+ const resolver = t.context.pathwayResolver;
443
+
444
+ // Initial data from first tool call
445
+ resolver.pathwayResultData = {
446
+ citations: ['doc1'],
447
+ toolCalls: [{ name: 'fetch_data', args: { id: 1 } }],
448
+ usage: { tokens: 50, prompt_tokens: 25 },
449
+ toolUsed: 'fetch_data'
450
+ };
451
+
452
+ // CortexResponse from second tool call
453
+ const cortexResponse = new CortexResponse({
454
+ output_text: 'Analysis complete',
455
+ citations: ['doc2', 'doc3'],
456
+ toolCalls: [{ name: 'analyze_data', args: { data: 'fetched' } }],
457
+ usage: { tokens: 100, prompt_tokens: 50 },
458
+ finishReason: 'stop'
459
+ });
460
+
461
+ const result = resolver.mergeResultData(cortexResponse);
462
+
463
+ t.deepEqual(result.citations, ['doc1', 'doc2', 'doc3']);
464
+ t.is(result.toolCalls.length, 2);
465
+ t.is(result.toolCalls[0].name, 'fetch_data');
466
+ t.is(result.toolCalls[1].name, 'analyze_data');
467
+ t.is(Array.isArray(result.usage), true);
468
+ t.is(result.usage.length, 2);
469
+ t.deepEqual(result.usage[0], { tokens: 100, prompt_tokens: 50 }); // New first
470
+ t.deepEqual(result.usage[1], { tokens: 50, prompt_tokens: 25 }); // Old second
471
+ t.is(result.finishReason, 'stop');
472
+ });
473
+
474
+ // ============================================================================
475
+ // Edge Cases and Error Handling
476
+ // ============================================================================
477
+
478
+ test('mergeResultData handles malformed CortexResponse objects', (t) => {
479
+ const resolver = t.context.pathwayResolver;
480
+ resolver.pathwayResultData = { citations: ['cite1'] };
481
+
482
+ // Create a mock object that looks like CortexResponse but isn't
483
+ const mockCortexResponse = {
484
+ constructor: { name: 'CortexResponse' },
485
+ citations: ['cite2'],
486
+ usage: { tokens: 100 }
487
+ };
488
+
489
+ const result = resolver.mergeResultData(mockCortexResponse);
490
+
491
+ t.deepEqual(result.citations, ['cite1', 'cite2']);
492
+ t.deepEqual(result.usage, [{ tokens: 100 }]); // Should be converted to array
493
+ });
494
+
495
+ test('mergeResultData handles deeply nested objects', (t) => {
496
+ const resolver = t.context.pathwayResolver;
497
+ resolver.pathwayResultData = {
498
+ metadata: {
499
+ nested: {
500
+ deep: {
501
+ value: 'original'
502
+ }
503
+ }
504
+ }
505
+ };
506
+
507
+ const newData = {
508
+ metadata: {
509
+ nested: {
510
+ deep: {
511
+ value: 'updated',
512
+ newField: 'added'
513
+ }
514
+ }
515
+ }
516
+ };
517
+
518
+ const result = resolver.mergeResultData(newData);
519
+
520
+ t.deepEqual(result.metadata.nested.deep.value, 'updated');
521
+ t.is(result.metadata.nested.deep.newField, 'added');
522
+ });
523
+
524
+ test('mergeResultData handles circular references gracefully', (t) => {
525
+ const resolver = t.context.pathwayResolver;
526
+ resolver.pathwayResultData = { citations: ['cite1'] };
527
+
528
+ const newData = { citations: ['cite2'] };
529
+ newData.self = newData; // Create circular reference
530
+
531
+ const result = resolver.mergeResultData(newData);
532
+
533
+ t.deepEqual(result.citations, ['cite1', 'cite2']);
534
+ t.is(result.self, newData); // Should handle circular reference
535
+ });
536
+
537
+ // ============================================================================
538
+ // Performance and Memory Tests
539
+ // ============================================================================
540
+
541
+ test('mergeResultData handles large arrays efficiently', (t) => {
542
+ const resolver = t.context.pathwayResolver;
543
+
544
+ // Create large arrays
545
+ const largeCitations = Array.from({ length: 1000 }, (_, i) => `cite${i}`);
546
+ const largeToolCalls = Array.from({ length: 100 }, (_, i) => ({
547
+ name: `tool${i}`,
548
+ args: { id: i }
549
+ }));
550
+
551
+ resolver.pathwayResultData = {
552
+ citations: largeCitations.slice(0, 500),
553
+ toolCalls: largeToolCalls.slice(0, 50)
554
+ };
555
+
556
+ const newData = {
557
+ citations: largeCitations.slice(500),
558
+ toolCalls: largeToolCalls.slice(50)
559
+ };
560
+
561
+ const result = resolver.mergeResultData(newData);
562
+
563
+ t.is(result.citations.length, 1000);
564
+ t.is(result.toolCalls.length, 100);
565
+ t.is(result.citations[0], 'cite0');
566
+ t.is(result.citations[999], 'cite999');
567
+ });
568
+
569
+ test('mergeResolver handles multiple sequential merges correctly', (t) => {
570
+ const resolver = t.context.pathwayResolver;
571
+
572
+ // First merge
573
+ const resolver1 = new PathwayResolver({
574
+ config: mockConfig,
575
+ pathway: mockPathway,
576
+ args: mockArgs,
577
+ endpoints: mockModelEndpoints,
578
+ });
579
+ resolver1.pathwayResultData = { citations: ['cite1'], usage: { tokens: 100 } };
580
+ resolver.mergeResolver(resolver1);
581
+
582
+ // Second merge
583
+ const resolver2 = new PathwayResolver({
584
+ config: mockConfig,
585
+ pathway: mockPathway,
586
+ args: mockArgs,
587
+ endpoints: mockModelEndpoints,
588
+ });
589
+ resolver2.pathwayResultData = { citations: ['cite2'], usage: { tokens: 200 } };
590
+ resolver.mergeResolver(resolver2);
591
+
592
+ // Third merge
593
+ const resolver3 = new PathwayResolver({
594
+ config: mockConfig,
595
+ pathway: mockPathway,
596
+ args: mockArgs,
597
+ endpoints: mockModelEndpoints,
598
+ });
599
+ resolver3.pathwayResultData = { citations: ['cite3'], usage: { tokens: 300 } };
600
+ resolver.mergeResolver(resolver3);
601
+
602
+ const result = resolver.pathwayResultData;
603
+
604
+ t.deepEqual(result.citations, ['cite1', 'cite2', 'cite3']);
605
+ t.is(Array.isArray(result.usage), true);
606
+ t.is(result.usage.length, 3);
607
+ t.deepEqual(result.usage[0], { tokens: 300 }); // Most recent first
608
+ t.deepEqual(result.usage[1], { tokens: 200 });
609
+ t.deepEqual(result.usage[2], { tokens: 100 });
610
+ });
611
+
612
+ // ============================================================================
613
+ // Additional Edge Cases
614
+ // ============================================================================
615
+
616
+ test('mergeResultData handles undefined array fields correctly', (t) => {
617
+ const resolver = t.context.pathwayResolver;
618
+ resolver.pathwayResultData = {
619
+ citations: undefined,
620
+ toolCalls: undefined
621
+ };
622
+
623
+ const newData = {
624
+ citations: ['cite1'],
625
+ toolCalls: [{ name: 'tool1', args: {} }]
626
+ };
627
+
628
+ const result = resolver.mergeResultData(newData);
629
+
630
+ t.deepEqual(result.citations, ['cite1']);
631
+ t.deepEqual(result.toolCalls, [{ name: 'tool1', args: {} }]);
632
+ });
633
+
634
+ test('mergeResultData handles empty string citations', (t) => {
635
+ const resolver = t.context.pathwayResolver;
636
+ resolver.pathwayResultData = {
637
+ citations: ['']
638
+ };
639
+
640
+ const newData = {
641
+ citations: ['cite1']
642
+ };
643
+
644
+ const result = resolver.mergeResultData(newData);
645
+
646
+ t.deepEqual(result.citations, ['', 'cite1']);
647
+ });
648
+
649
+ test('mergeResultData handles complex nested toolCalls', (t) => {
650
+ const resolver = t.context.pathwayResolver;
651
+ resolver.pathwayResultData = {
652
+ toolCalls: [{
653
+ name: 'search_tool',
654
+ args: { query: 'test', filters: { category: 'tech' } },
655
+ id: 'call_1'
656
+ }]
657
+ };
658
+
659
+ const newData = {
660
+ toolCalls: [{
661
+ name: 'analyze_tool',
662
+ args: { data: 'search_results', options: { deep: true } },
663
+ id: 'call_2'
664
+ }]
665
+ };
666
+
667
+ const result = resolver.mergeResultData(newData);
668
+
669
+ t.is(result.toolCalls.length, 2);
670
+ t.is(result.toolCalls[0].name, 'search_tool');
671
+ t.is(result.toolCalls[0].args.query, 'test');
672
+ t.is(result.toolCalls[0].args.filters.category, 'tech');
673
+ t.is(result.toolCalls[1].name, 'analyze_tool');
674
+ t.is(result.toolCalls[1].args.data, 'search_results');
675
+ t.is(result.toolCalls[1].args.options.deep, true);
676
+ });
677
+
678
+ test('mergeResultData preserves non-standard fields', (t) => {
679
+ const resolver = t.context.pathwayResolver;
680
+ resolver.pathwayResultData = {
681
+ customField: 'original',
682
+ nestedData: { value: 1, items: ['a', 'b'] }
683
+ };
684
+
685
+ const newData = {
686
+ customField: 'updated',
687
+ nestedData: { value: 2, items: ['c', 'd'] },
688
+ newField: 'added'
689
+ };
690
+
691
+ const result = resolver.mergeResultData(newData);
692
+
693
+ t.is(result.customField, 'updated');
694
+ t.is(result.nestedData.value, 2);
695
+ t.deepEqual(result.nestedData.items, ['c', 'd']);
696
+ t.is(result.newField, 'added');
697
+ });
698
+
699
+ test('mergeResolver handles resolver with no pathwayResultData', (t) => {
700
+ const resolver1 = t.context.pathwayResolver;
701
+ const resolver2 = new PathwayResolver({
702
+ config: mockConfig,
703
+ pathway: mockPathway,
704
+ args: mockArgs,
705
+ endpoints: mockModelEndpoints,
706
+ });
707
+
708
+ resolver1.pathwayResultData = { citations: ['cite1'] };
709
+ resolver2.pathwayResultData = null;
710
+
711
+ resolver1.mergeResolver(resolver2);
712
+
713
+ t.deepEqual(resolver1.pathwayResultData, { citations: ['cite1'] });
714
+ });
715
+
716
+ test('mergeResultData handles boolean and numeric values in usage', (t) => {
717
+ const resolver = t.context.pathwayResolver;
718
+ resolver.pathwayResultData = {
719
+ usage: { tokens: 100, cached: true, cost: 0.05 }
720
+ };
721
+
722
+ const newData = {
723
+ usage: { tokens: 200, cached: false, cost: 0.10 }
724
+ };
725
+
726
+ const result = resolver.mergeResultData(newData);
727
+
728
+ t.is(Array.isArray(result.usage), true);
729
+ t.is(result.usage.length, 2);
730
+ t.deepEqual(result.usage[0], { tokens: 200, cached: false, cost: 0.10 });
731
+ t.deepEqual(result.usage[1], { tokens: 100, cached: true, cost: 0.05 });
732
+ });
733
+
734
+ // ============================================================================
735
+ // Tool Property Integration Tests
736
+ // ============================================================================
737
+
738
+ test('tool property setter integrates data correctly via mergeResultData', (t) => {
739
+ const resolver = t.context.pathwayResolver;
740
+
741
+ // Set initial data
742
+ resolver.pathwayResultData = {
743
+ citations: ['cite1'],
744
+ usage: { tokens: 100 }
745
+ };
746
+
747
+ // Set tool property with JSON string
748
+ const toolData = {
749
+ toolUsed: 'search_tool',
750
+ citations: ['cite2'],
751
+ title: 'Test Title',
752
+ search: { query: 'test query' },
753
+ coding: true
754
+ };
755
+
756
+ resolver.tool = JSON.stringify(toolData);
757
+
758
+ // Verify data was merged correctly
759
+ t.deepEqual(resolver.pathwayResultData.citations, ['cite1', 'cite2']);
760
+ t.deepEqual(resolver.pathwayResultData.toolUsed, ['search_tool']); // Should be converted to array
761
+ t.is(resolver.pathwayResultData.title, 'Test Title');
762
+ t.deepEqual(resolver.pathwayResultData.search, { query: 'test query' });
763
+ t.is(resolver.pathwayResultData.coding, true);
764
+ t.deepEqual(resolver.pathwayResultData.usage, [{ tokens: 100 }]); // Should be converted to array
765
+ });
766
+
767
+ test('tool property setter handles object input directly', (t) => {
768
+ const resolver = t.context.pathwayResolver;
769
+
770
+ // Set tool property with object (not JSON string)
771
+ const toolData = {
772
+ toolUsed: 'analyze_tool',
773
+ citations: ['cite1'],
774
+ hideFromModel: true,
775
+ toolCallbackName: 'test_callback'
776
+ };
777
+
778
+ resolver.tool = toolData;
779
+
780
+ // Verify data was merged correctly
781
+ t.deepEqual(resolver.pathwayResultData.citations, ['cite1']);
782
+ t.deepEqual(resolver.pathwayResultData.toolUsed, ['analyze_tool']);
783
+ t.is(resolver.pathwayResultData.hideFromModel, true);
784
+ t.is(resolver.pathwayResultData.toolCallbackName, 'test_callback');
785
+ });
786
+
787
+ test('tool property getter returns correct legacy fields', (t) => {
788
+ const resolver = t.context.pathwayResolver;
789
+
790
+ // Set up pathwayResultData with various fields
791
+ resolver.pathwayResultData = {
792
+ hideFromModel: true,
793
+ toolCallbackName: 'test_callback',
794
+ title: 'Test Title',
795
+ search: { query: 'test' },
796
+ coding: false,
797
+ codeRequestId: 'code_123',
798
+ toolCallbackId: 'callback_456',
799
+ toolUsed: ['tool1', 'tool2'],
800
+ citations: ['cite1', 'cite2'],
801
+ // These should be excluded from tool getter
802
+ usage: { tokens: 100 },
803
+ finishReason: 'stop',
804
+ customField: 'should_not_appear'
805
+ };
806
+
807
+ const toolString = resolver.tool;
808
+ const toolData = JSON.parse(toolString);
809
+
810
+ // Verify only legacy fields are included
811
+ t.is(toolData.hideFromModel, true);
812
+ t.is(toolData.toolCallbackName, 'test_callback');
813
+ t.is(toolData.title, 'Test Title');
814
+ t.deepEqual(toolData.search, { query: 'test' });
815
+ t.is(toolData.coding, false);
816
+ t.is(toolData.codeRequestId, 'code_123');
817
+ t.is(toolData.toolCallbackId, 'callback_456');
818
+ t.deepEqual(toolData.toolUsed, ['tool1', 'tool2']);
819
+ t.deepEqual(toolData.citations, ['cite1', 'cite2']);
820
+
821
+ // Verify excluded fields are not present
822
+ t.is(toolData.usage, undefined);
823
+ t.is(toolData.finishReason, undefined);
824
+ t.is(toolData.customField, undefined);
825
+ });
826
+
827
+ test('tool property getter excludes undefined fields', (t) => {
828
+ const resolver = t.context.pathwayResolver;
829
+
830
+ // Set up pathwayResultData with only some fields defined
831
+ resolver.pathwayResultData = {
832
+ title: 'Test Title',
833
+ citations: ['cite1'],
834
+ // Other legacy fields are undefined
835
+ };
836
+
837
+ const toolString = resolver.tool;
838
+ const toolData = JSON.parse(toolString);
839
+
840
+ // Verify only defined fields are included
841
+ t.is(toolData.title, 'Test Title');
842
+ t.deepEqual(toolData.citations, ['cite1']);
843
+
844
+ // Verify undefined fields are excluded
845
+ t.is(toolData.hideFromModel, undefined);
846
+ t.is(toolData.toolCallbackName, undefined);
847
+ t.is(toolData.search, undefined);
848
+ t.is(toolData.coding, undefined);
849
+ t.is(toolData.codeRequestId, undefined);
850
+ t.is(toolData.toolCallbackId, undefined);
851
+ t.is(toolData.toolUsed, undefined);
852
+ });
853
+
854
+ test('tool property setter handles invalid JSON gracefully', (t) => {
855
+ const resolver = t.context.pathwayResolver;
856
+
857
+ // Set initial data
858
+ resolver.pathwayResultData = {
859
+ citations: ['cite1'],
860
+ title: 'Original Title'
861
+ };
862
+
863
+ // Mock console.warn to capture warning
864
+ const originalWarn = console.warn;
865
+ let warningCalled = false;
866
+ console.warn = (message) => {
867
+ warningCalled = true;
868
+ t.true(message.includes('Invalid tool property assignment'));
869
+ };
870
+
871
+ // Set tool property with invalid JSON
872
+ resolver.tool = '{"invalid": json}';
873
+
874
+ // Verify original data is preserved
875
+ t.deepEqual(resolver.pathwayResultData.citations, ['cite1']);
876
+ t.is(resolver.pathwayResultData.title, 'Original Title');
877
+ t.true(warningCalled);
878
+
879
+ // Restore console.warn
880
+ console.warn = originalWarn;
881
+ });
882
+
883
+ test('tool property integration with mergeResolver', (t) => {
884
+ const resolver1 = t.context.pathwayResolver;
885
+ const resolver2 = new PathwayResolver({
886
+ config: mockConfig,
887
+ pathway: mockPathway,
888
+ args: mockArgs,
889
+ endpoints: mockModelEndpoints,
890
+ });
891
+
892
+ // Set up resolver1 with tool data
893
+ resolver1.pathwayResultData = {
894
+ citations: ['cite1'],
895
+ title: 'Original Title'
896
+ };
897
+ resolver1.tool = JSON.stringify({
898
+ toolUsed: 'search_tool',
899
+ citations: ['cite2'],
900
+ title: 'Updated Title'
901
+ });
902
+
903
+ // Set up resolver2 with different tool data
904
+ resolver2.pathwayResultData = {
905
+ citations: ['cite3'],
906
+ coding: true
907
+ };
908
+ resolver2.tool = JSON.stringify({
909
+ toolUsed: 'analyze_tool',
910
+ citations: ['cite4'],
911
+ hideFromModel: true
912
+ });
913
+
914
+ // Merge resolver2 into resolver1
915
+ resolver1.mergeResolver(resolver2);
916
+
917
+ // Verify merged data
918
+ t.deepEqual(resolver1.pathwayResultData.citations, ['cite1', 'cite2', 'cite3', 'cite4']);
919
+ t.is(resolver1.pathwayResultData.title, 'Updated Title'); // From resolver1's tool setter
920
+ t.is(resolver1.pathwayResultData.coding, true); // From resolver2
921
+ t.is(resolver1.pathwayResultData.hideFromModel, true); // From resolver2's tool setter
922
+ t.deepEqual(resolver1.pathwayResultData.toolUsed, ['analyze_tool', 'search_tool']); // Both merged, newest first
923
+ });
924
+
925
+ test('tool property handles complex nested data', (t) => {
926
+ const resolver = t.context.pathwayResolver;
927
+
928
+ const complexToolData = {
929
+ toolUsed: 'complex_tool',
930
+ citations: ['cite1', 'cite2'],
931
+ search: {
932
+ query: 'complex search',
933
+ filters: {
934
+ category: 'tech',
935
+ dateRange: { start: '2024-01-01', end: '2024-12-31' }
936
+ }
937
+ },
938
+ title: 'Complex Analysis',
939
+ coding: true,
940
+ hideFromModel: false
941
+ };
942
+
943
+ resolver.tool = JSON.stringify(complexToolData);
944
+
945
+ // Verify complex data was merged correctly
946
+ t.deepEqual(resolver.pathwayResultData.toolUsed, ['complex_tool']);
947
+ t.deepEqual(resolver.pathwayResultData.citations, ['cite1', 'cite2']);
948
+ t.deepEqual(resolver.pathwayResultData.search, complexToolData.search);
949
+ t.is(resolver.pathwayResultData.title, 'Complex Analysis');
950
+ t.is(resolver.pathwayResultData.coding, true);
951
+ t.is(resolver.pathwayResultData.hideFromModel, false);
952
+ });