illuma-agents 1.0.8 → 1.0.10

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 (250) hide show
  1. package/LICENSE +1 -5
  2. package/dist/cjs/common/enum.cjs +1 -2
  3. package/dist/cjs/common/enum.cjs.map +1 -1
  4. package/dist/cjs/events.cjs +11 -0
  5. package/dist/cjs/events.cjs.map +1 -1
  6. package/dist/cjs/graphs/Graph.cjs +2 -1
  7. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  8. package/dist/cjs/instrumentation.cjs +3 -1
  9. package/dist/cjs/instrumentation.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  11. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -2
  12. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
  14. package/dist/cjs/llm/bedrock/index.cjs +99 -0
  15. package/dist/cjs/llm/bedrock/index.cjs.map +1 -0
  16. package/dist/cjs/llm/fake.cjs.map +1 -1
  17. package/dist/cjs/llm/openai/index.cjs +102 -0
  18. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  19. package/dist/cjs/llm/openai/utils/index.cjs +87 -1
  20. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  21. package/dist/cjs/llm/openrouter/index.cjs +175 -1
  22. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  23. package/dist/cjs/llm/providers.cjs +13 -16
  24. package/dist/cjs/llm/providers.cjs.map +1 -1
  25. package/dist/cjs/llm/text.cjs.map +1 -1
  26. package/dist/cjs/messages/core.cjs +14 -14
  27. package/dist/cjs/messages/core.cjs.map +1 -1
  28. package/dist/cjs/messages/ids.cjs.map +1 -1
  29. package/dist/cjs/messages/prune.cjs.map +1 -1
  30. package/dist/cjs/run.cjs +18 -1
  31. package/dist/cjs/run.cjs.map +1 -1
  32. package/dist/cjs/splitStream.cjs.map +1 -1
  33. package/dist/cjs/stream.cjs +24 -1
  34. package/dist/cjs/stream.cjs.map +1 -1
  35. package/dist/cjs/tools/ToolNode.cjs +20 -1
  36. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  37. package/dist/cjs/tools/handlers.cjs +29 -25
  38. package/dist/cjs/tools/handlers.cjs.map +1 -1
  39. package/dist/cjs/tools/search/anthropic.cjs.map +1 -1
  40. package/dist/cjs/tools/search/content.cjs.map +1 -1
  41. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  42. package/dist/cjs/tools/search/format.cjs.map +1 -1
  43. package/dist/cjs/tools/search/highlights.cjs.map +1 -1
  44. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  45. package/dist/cjs/tools/search/schema.cjs +27 -25
  46. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  47. package/dist/cjs/tools/search/search.cjs +6 -1
  48. package/dist/cjs/tools/search/search.cjs.map +1 -1
  49. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
  50. package/dist/cjs/tools/search/tool.cjs +182 -35
  51. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  52. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  53. package/dist/cjs/utils/graph.cjs.map +1 -1
  54. package/dist/cjs/utils/llm.cjs +0 -1
  55. package/dist/cjs/utils/llm.cjs.map +1 -1
  56. package/dist/cjs/utils/misc.cjs.map +1 -1
  57. package/dist/cjs/utils/run.cjs.map +1 -1
  58. package/dist/cjs/utils/title.cjs +7 -7
  59. package/dist/cjs/utils/title.cjs.map +1 -1
  60. package/dist/esm/common/enum.mjs +1 -2
  61. package/dist/esm/common/enum.mjs.map +1 -1
  62. package/dist/esm/events.mjs +11 -0
  63. package/dist/esm/events.mjs.map +1 -1
  64. package/dist/esm/graphs/Graph.mjs +2 -1
  65. package/dist/esm/graphs/Graph.mjs.map +1 -1
  66. package/dist/esm/instrumentation.mjs +3 -1
  67. package/dist/esm/instrumentation.mjs.map +1 -1
  68. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  69. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -2
  70. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  71. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
  72. package/dist/esm/llm/bedrock/index.mjs +97 -0
  73. package/dist/esm/llm/bedrock/index.mjs.map +1 -0
  74. package/dist/esm/llm/fake.mjs.map +1 -1
  75. package/dist/esm/llm/openai/index.mjs +103 -1
  76. package/dist/esm/llm/openai/index.mjs.map +1 -1
  77. package/dist/esm/llm/openai/utils/index.mjs +88 -2
  78. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  79. package/dist/esm/llm/openrouter/index.mjs +175 -1
  80. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  81. package/dist/esm/llm/providers.mjs +2 -5
  82. package/dist/esm/llm/providers.mjs.map +1 -1
  83. package/dist/esm/llm/text.mjs.map +1 -1
  84. package/dist/esm/messages/core.mjs +14 -14
  85. package/dist/esm/messages/core.mjs.map +1 -1
  86. package/dist/esm/messages/ids.mjs.map +1 -1
  87. package/dist/esm/messages/prune.mjs.map +1 -1
  88. package/dist/esm/run.mjs +18 -1
  89. package/dist/esm/run.mjs.map +1 -1
  90. package/dist/esm/splitStream.mjs.map +1 -1
  91. package/dist/esm/stream.mjs +24 -1
  92. package/dist/esm/stream.mjs.map +1 -1
  93. package/dist/esm/tools/ToolNode.mjs +20 -1
  94. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  95. package/dist/esm/tools/handlers.mjs +30 -26
  96. package/dist/esm/tools/handlers.mjs.map +1 -1
  97. package/dist/esm/tools/search/anthropic.mjs.map +1 -1
  98. package/dist/esm/tools/search/content.mjs.map +1 -1
  99. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  100. package/dist/esm/tools/search/format.mjs.map +1 -1
  101. package/dist/esm/tools/search/highlights.mjs.map +1 -1
  102. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  103. package/dist/esm/tools/search/schema.mjs +27 -25
  104. package/dist/esm/tools/search/schema.mjs.map +1 -1
  105. package/dist/esm/tools/search/search.mjs +6 -1
  106. package/dist/esm/tools/search/search.mjs.map +1 -1
  107. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
  108. package/dist/esm/tools/search/tool.mjs +182 -35
  109. package/dist/esm/tools/search/tool.mjs.map +1 -1
  110. package/dist/esm/tools/search/utils.mjs.map +1 -1
  111. package/dist/esm/utils/graph.mjs.map +1 -1
  112. package/dist/esm/utils/llm.mjs +0 -1
  113. package/dist/esm/utils/llm.mjs.map +1 -1
  114. package/dist/esm/utils/misc.mjs.map +1 -1
  115. package/dist/esm/utils/run.mjs.map +1 -1
  116. package/dist/esm/utils/title.mjs +7 -7
  117. package/dist/esm/utils/title.mjs.map +1 -1
  118. package/dist/types/common/enum.d.ts +1 -2
  119. package/dist/types/llm/bedrock/index.d.ts +36 -0
  120. package/dist/types/llm/openai/index.d.ts +1 -0
  121. package/dist/types/llm/openai/utils/index.d.ts +10 -1
  122. package/dist/types/llm/openrouter/index.d.ts +4 -1
  123. package/dist/types/tools/search/types.d.ts +2 -0
  124. package/dist/types/types/llm.d.ts +3 -8
  125. package/package.json +16 -12
  126. package/src/common/enum.ts +1 -2
  127. package/src/common/index.ts +1 -1
  128. package/src/events.ts +11 -0
  129. package/src/graphs/Graph.ts +2 -1
  130. package/src/instrumentation.ts +25 -22
  131. package/src/llm/anthropic/llm.spec.ts +1442 -1442
  132. package/src/llm/anthropic/types.ts +140 -140
  133. package/src/llm/anthropic/utils/message_inputs.ts +757 -660
  134. package/src/llm/anthropic/utils/output_parsers.ts +133 -133
  135. package/src/llm/anthropic/utils/tools.ts +29 -29
  136. package/src/llm/bedrock/index.ts +128 -0
  137. package/src/llm/fake.ts +133 -133
  138. package/src/llm/google/llm.spec.ts +3 -1
  139. package/src/llm/google/utils/tools.ts +160 -160
  140. package/src/llm/openai/index.ts +126 -0
  141. package/src/llm/openai/types.ts +24 -24
  142. package/src/llm/openai/utils/index.ts +116 -1
  143. package/src/llm/openai/utils/isReasoningModel.test.ts +90 -90
  144. package/src/llm/openrouter/index.ts +222 -1
  145. package/src/llm/providers.ts +2 -7
  146. package/src/llm/text.ts +94 -94
  147. package/src/messages/core.ts +463 -463
  148. package/src/messages/formatAgentMessages.tools.test.ts +400 -400
  149. package/src/messages/formatMessage.test.ts +693 -693
  150. package/src/messages/ids.ts +26 -26
  151. package/src/messages/prune.ts +567 -567
  152. package/src/messages/shiftIndexTokenCountMap.test.ts +81 -81
  153. package/src/mockStream.ts +98 -98
  154. package/src/prompts/collab.ts +5 -5
  155. package/src/prompts/index.ts +1 -1
  156. package/src/prompts/taskmanager.ts +61 -61
  157. package/src/run.ts +22 -4
  158. package/src/scripts/ant_web_search_edge_case.ts +162 -0
  159. package/src/scripts/ant_web_search_error_edge_case.ts +148 -0
  160. package/src/scripts/args.ts +48 -48
  161. package/src/scripts/caching.ts +123 -123
  162. package/src/scripts/code_exec_files.ts +193 -193
  163. package/src/scripts/empty_input.ts +137 -137
  164. package/src/scripts/memory.ts +97 -97
  165. package/src/scripts/test-tools-before-handoff.ts +1 -5
  166. package/src/scripts/thinking.ts +149 -149
  167. package/src/scripts/tools.ts +1 -4
  168. package/src/specs/anthropic.simple.test.ts +67 -0
  169. package/src/specs/spec.utils.ts +3 -3
  170. package/src/specs/token-distribution-edge-case.test.ts +316 -316
  171. package/src/specs/tool-error.test.ts +193 -193
  172. package/src/splitStream.test.ts +691 -691
  173. package/src/splitStream.ts +234 -234
  174. package/src/stream.test.ts +94 -94
  175. package/src/stream.ts +30 -1
  176. package/src/tools/ToolNode.ts +24 -1
  177. package/src/tools/handlers.ts +32 -28
  178. package/src/tools/search/anthropic.ts +51 -51
  179. package/src/tools/search/content.test.ts +173 -173
  180. package/src/tools/search/content.ts +147 -147
  181. package/src/tools/search/direct-url.test.ts +530 -0
  182. package/src/tools/search/firecrawl.ts +210 -210
  183. package/src/tools/search/format.ts +250 -250
  184. package/src/tools/search/highlights.ts +320 -320
  185. package/src/tools/search/index.ts +2 -2
  186. package/src/tools/search/jina-reranker.test.ts +126 -126
  187. package/src/tools/search/output.md +2775 -2775
  188. package/src/tools/search/rerankers.ts +242 -242
  189. package/src/tools/search/schema.ts +65 -63
  190. package/src/tools/search/search.ts +766 -759
  191. package/src/tools/search/serper-scraper.ts +155 -155
  192. package/src/tools/search/test.html +883 -883
  193. package/src/tools/search/test.md +642 -642
  194. package/src/tools/search/test.ts +159 -159
  195. package/src/tools/search/tool.ts +641 -471
  196. package/src/tools/search/types.ts +689 -687
  197. package/src/tools/search/utils.ts +79 -79
  198. package/src/types/index.ts +6 -6
  199. package/src/types/llm.ts +2 -8
  200. package/src/utils/graph.ts +10 -10
  201. package/src/utils/llm.ts +26 -27
  202. package/src/utils/llmConfig.ts +13 -5
  203. package/src/utils/logging.ts +48 -48
  204. package/src/utils/misc.ts +57 -57
  205. package/src/utils/run.ts +100 -100
  206. package/src/utils/title.ts +165 -165
  207. package/dist/cjs/llm/ollama/index.cjs +0 -70
  208. package/dist/cjs/llm/ollama/index.cjs.map +0 -1
  209. package/dist/cjs/llm/ollama/utils.cjs +0 -158
  210. package/dist/cjs/llm/ollama/utils.cjs.map +0 -1
  211. package/dist/esm/llm/ollama/index.mjs +0 -68
  212. package/dist/esm/llm/ollama/index.mjs.map +0 -1
  213. package/dist/esm/llm/ollama/utils.mjs +0 -155
  214. package/dist/esm/llm/ollama/utils.mjs.map +0 -1
  215. package/dist/types/llm/ollama/index.d.ts +0 -8
  216. package/dist/types/llm/ollama/utils.d.ts +0 -7
  217. package/src/llm/ollama/index.ts +0 -92
  218. package/src/llm/ollama/utils.ts +0 -193
  219. package/src/proto/CollabGraph.ts +0 -269
  220. package/src/proto/TaskManager.ts +0 -243
  221. package/src/proto/collab.ts +0 -200
  222. package/src/proto/collab_design.ts +0 -184
  223. package/src/proto/collab_design_v2.ts +0 -224
  224. package/src/proto/collab_design_v3.ts +0 -255
  225. package/src/proto/collab_design_v4.ts +0 -220
  226. package/src/proto/collab_design_v5.ts +0 -251
  227. package/src/proto/collab_graph.ts +0 -181
  228. package/src/proto/collab_original.ts +0 -123
  229. package/src/proto/example.ts +0 -93
  230. package/src/proto/example_new.ts +0 -68
  231. package/src/proto/example_old.ts +0 -201
  232. package/src/proto/example_test.ts +0 -152
  233. package/src/proto/example_test_anthropic.ts +0 -100
  234. package/src/proto/log_stream.ts +0 -202
  235. package/src/proto/main_collab_community_event.ts +0 -133
  236. package/src/proto/main_collab_design_v2.ts +0 -96
  237. package/src/proto/main_collab_design_v4.ts +0 -100
  238. package/src/proto/main_collab_design_v5.ts +0 -135
  239. package/src/proto/main_collab_global_analysis.ts +0 -122
  240. package/src/proto/main_collab_hackathon_event.ts +0 -153
  241. package/src/proto/main_collab_space_mission.ts +0 -153
  242. package/src/proto/main_philosophy.ts +0 -210
  243. package/src/proto/original_script.ts +0 -126
  244. package/src/proto/standard.ts +0 -100
  245. package/src/proto/stream.ts +0 -56
  246. package/src/proto/tasks.ts +0 -118
  247. package/src/proto/tools/global_analysis_tools.ts +0 -86
  248. package/src/proto/tools/space_mission_tools.ts +0 -60
  249. package/src/proto/vertexai.ts +0 -54
  250. package/src/scripts/image.ts +0 -178
package/src/stream.ts CHANGED
@@ -107,6 +107,25 @@ export function getChunkContent({
107
107
  | undefined
108
108
  )?.summary?.[0]?.text;
109
109
  }
110
+ if (
111
+ provider === Providers.OPENROUTER &&
112
+ chunk?.additional_kwargs?.reasoning_details != null &&
113
+ Array.isArray(chunk.additional_kwargs.reasoning_details)
114
+ ) {
115
+ // Extract text from reasoning_details array (for Gemini, DeepSeek, etc.)
116
+ const textEntries = chunk.additional_kwargs.reasoning_details
117
+ .filter(
118
+ (detail) =>
119
+ detail.type === 'reasoning.text' &&
120
+ detail.text != null &&
121
+ detail.text !== ''
122
+ )
123
+ .map((detail) => detail.text)
124
+ .join('');
125
+ if (textEntries) {
126
+ return textEntries;
127
+ }
128
+ }
110
129
  return (
111
130
  ((chunk?.additional_kwargs?.[reasoningKey] as string | undefined) ?? '') ||
112
131
  chunk?.content
@@ -155,7 +174,10 @@ export class ChatModelStreamHandler implements t.EventHandler {
155
174
  chunk.tool_calls.length > 0 &&
156
175
  chunk.tool_calls.every(
157
176
  (tc) =>
158
- tc.id != null && tc.id !== '' && tc.name != null && tc.name !== ''
177
+ tc.id != null &&
178
+ tc.id !== '' &&
179
+ (tc as Partial<ToolCall>).name != null &&
180
+ tc.name !== ''
159
181
  )
160
182
  ) {
161
183
  hasToolCalls = true;
@@ -352,6 +374,13 @@ hasToolCallChunks: ${hasToolCallChunks}
352
374
  reasoning_content.summary[0].text
353
375
  ) {
354
376
  reasoning_content = 'valid';
377
+ } else if (
378
+ agentContext.provider === Providers.OPENROUTER &&
379
+ chunk.additional_kwargs?.reasoning_details != null &&
380
+ Array.isArray(chunk.additional_kwargs.reasoning_details) &&
381
+ chunk.additional_kwargs.reasoning_details.length > 0
382
+ ) {
383
+ reasoning_content = 'valid';
355
384
  }
356
385
  if (
357
386
  reasoning_content != null &&
@@ -87,6 +87,18 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
87
87
  { ...call, args, type: 'tool_call', stepId, turn },
88
88
  config
89
89
  );
90
+
91
+ // Debug logging for image generation
92
+ if (call.name === 'image_generation') {
93
+ console.log('[ToolNode] image_generation output:', {
94
+ isBaseMessage: isBaseMessage(output),
95
+ messageType: isBaseMessage(output) ? output._getType() : 'not a message',
96
+ isCommand: isCommand(output),
97
+ hasArtifact: isBaseMessage(output) && (output as ToolMessage).artifact !== undefined,
98
+ outputType: typeof output,
99
+ });
100
+ }
101
+
90
102
  if (
91
103
  (isBaseMessage(output) && output._getType() === 'tool') ||
92
104
  isCommand(output)
@@ -201,7 +213,18 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
201
213
 
202
214
  outputs = await Promise.all(
203
215
  aiMessage.tool_calls
204
- ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
216
+ ?.filter((call) => {
217
+ /**
218
+ * Filter out:
219
+ * 1. Already processed tool calls (present in toolMessageIds)
220
+ * 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')
221
+ * which are executed by the provider's API and don't require invocation
222
+ */
223
+ return (
224
+ (call.id == null || !toolMessageIds.has(call.id)) &&
225
+ !(call.id?.startsWith('srvtoolu_') ?? false)
226
+ );
227
+ })
205
228
  .map((call) => this.runTool(call, config)) ?? []
206
229
  );
207
230
  }
@@ -9,7 +9,6 @@ import type { AgentContext } from '@/agents/AgentContext';
9
9
  import type * as t from '@/types';
10
10
  import {
11
11
  ToolCallTypes,
12
- ContentTypes,
13
12
  GraphEvents,
14
13
  StepTypes,
15
14
  Providers,
@@ -87,22 +86,33 @@ export async function handleToolCallChunks({
87
86
  const alreadyDispatched =
88
87
  prevRunStep?.type === StepTypes.MESSAGE_CREATION &&
89
88
  graph.messageStepHasToolCalls.has(prevStepId);
90
- if (!alreadyDispatched && tool_calls?.length === toolCallChunks.length) {
91
- await graph.dispatchMessageDelta(prevStepId, {
92
- content: [
93
- {
94
- type: ContentTypes.TEXT,
95
- text: '',
96
- tool_call_ids: tool_calls.map((tc) => tc.id ?? ''),
97
- },
98
- ],
99
- });
89
+
90
+ if (prevRunStep?.type === StepTypes.TOOL_CALLS) {
91
+ /**
92
+ * If previous step is already a tool_calls step, use that step ID
93
+ * This ensures tool call deltas are dispatched to the correct step
94
+ */
95
+ stepId = prevStepId;
96
+ } else if (
97
+ !alreadyDispatched &&
98
+ prevRunStep?.type === StepTypes.MESSAGE_CREATION
99
+ ) {
100
+ /**
101
+ * Create tool_calls step as soon as we receive the first tool call chunk
102
+ * This ensures deltas are always associated with the correct step
103
+ *
104
+ * NOTE: We do NOT dispatch an empty text block here because:
105
+ * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages
106
+ * - The tool_calls themselves are sufficient for the step
107
+ * - Empty content with tool_call_ids gets stored in conversation history
108
+ * and causes "messages must have non-empty content" errors on replay
109
+ */
100
110
  graph.messageStepHasToolCalls.set(prevStepId, true);
101
111
  stepId = await graph.dispatchRunStep(
102
112
  stepKey,
103
113
  {
104
114
  type: StepTypes.TOOL_CALLS,
105
- tool_calls,
115
+ tool_calls: tool_calls ?? [],
106
116
  },
107
117
  metadata
108
118
  );
@@ -149,26 +159,21 @@ export const handleToolCalls = async (
149
159
  // no previous step
150
160
  }
151
161
 
152
- const dispatchToolCallIds = async (
153
- lastMessageStepId: string
154
- ): Promise<void> => {
155
- await graph.dispatchMessageDelta(lastMessageStepId, {
156
- content: [
157
- {
158
- type: 'text',
159
- text: '',
160
- tool_call_ids: [toolCallId],
161
- },
162
- ],
163
- });
164
- };
162
+ /**
163
+ * NOTE: We do NOT dispatch empty text blocks with tool_call_ids because:
164
+ * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages
165
+ * - They get stored in conversation history and cause errors on replay:
166
+ * "messages must have non-empty content" (Anthropic)
167
+ * "The content field in the Message object is empty" (Bedrock)
168
+ * - The tool_calls themselves are sufficient
169
+ */
170
+
165
171
  /* If the previous step exists and is a message creation */
166
172
  if (
167
173
  prevStepId &&
168
174
  prevRunStep &&
169
175
  prevRunStep.type === StepTypes.MESSAGE_CREATION
170
176
  ) {
171
- await dispatchToolCallIds(prevStepId);
172
177
  graph.messageStepHasToolCalls.set(prevStepId, true);
173
178
  /* If the previous step doesn't exist or is not a message creation */
174
179
  } else if (
@@ -186,8 +191,7 @@ export const handleToolCalls = async (
186
191
  },
187
192
  metadata
188
193
  );
189
- await dispatchToolCallIds(stepId);
190
- graph.messageStepHasToolCalls.set(prevStepId, true);
194
+ graph.messageStepHasToolCalls.set(stepId, true);
191
195
  }
192
196
 
193
197
  await graph.dispatchRunStep(
@@ -1,51 +1,51 @@
1
- import type {
2
- AnthropicTextBlockParam,
3
- AnthropicWebSearchResultBlockParam,
4
- } from '@/llm/anthropic/types';
5
- import type { SearchResultData, ProcessedOrganic } from './types';
6
- import { getAttribution } from './utils';
7
-
8
- /**
9
- * Coerces Anthropic web search results to the SearchResultData format
10
- * @param results - Array of Anthropic web search results
11
- * @param turn - The turn number to associate with these results
12
- * @returns SearchResultData with minimal ProcessedOrganic items
13
- */
14
- export function coerceAnthropicSearchResults({
15
- results,
16
- turn = 0,
17
- }: {
18
- results: (AnthropicTextBlockParam | AnthropicWebSearchResultBlockParam)[];
19
- turn?: number;
20
- }): SearchResultData {
21
- const organic: ProcessedOrganic[] = results
22
- .filter((result) => result.type === 'web_search_result')
23
- .map((result, index) => ({
24
- link: result.url,
25
- position: index + 1,
26
- title: result.title,
27
- date: result.page_age ?? undefined,
28
- attribution: getAttribution(result.url),
29
- }));
30
-
31
- return {
32
- turn,
33
- organic,
34
- };
35
- }
36
-
37
- /**
38
- * Helper function to check if an object is an Anthropic web search result
39
- */
40
- export function isAnthropicWebSearchResult(
41
- obj: unknown
42
- ): obj is AnthropicWebSearchResultBlockParam {
43
- return (
44
- typeof obj === 'object' &&
45
- obj !== null &&
46
- 'type' in obj &&
47
- obj.type === 'web_search_result' &&
48
- 'url' in obj &&
49
- typeof (obj as Record<string, unknown>).url === 'string'
50
- );
51
- }
1
+ import type {
2
+ AnthropicTextBlockParam,
3
+ AnthropicWebSearchResultBlockParam,
4
+ } from '@/llm/anthropic/types';
5
+ import type { SearchResultData, ProcessedOrganic } from './types';
6
+ import { getAttribution } from './utils';
7
+
8
+ /**
9
+ * Coerces Anthropic web search results to the SearchResultData format
10
+ * @param results - Array of Anthropic web search results
11
+ * @param turn - The turn number to associate with these results
12
+ * @returns SearchResultData with minimal ProcessedOrganic items
13
+ */
14
+ export function coerceAnthropicSearchResults({
15
+ results,
16
+ turn = 0,
17
+ }: {
18
+ results: (AnthropicTextBlockParam | AnthropicWebSearchResultBlockParam)[];
19
+ turn?: number;
20
+ }): SearchResultData {
21
+ const organic: ProcessedOrganic[] = results
22
+ .filter((result) => result.type === 'web_search_result')
23
+ .map((result, index) => ({
24
+ link: result.url,
25
+ position: index + 1,
26
+ title: result.title,
27
+ date: result.page_age ?? undefined,
28
+ attribution: getAttribution(result.url),
29
+ }));
30
+
31
+ return {
32
+ turn,
33
+ organic,
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Helper function to check if an object is an Anthropic web search result
39
+ */
40
+ export function isAnthropicWebSearchResult(
41
+ obj: unknown
42
+ ): obj is AnthropicWebSearchResultBlockParam {
43
+ return (
44
+ typeof obj === 'object' &&
45
+ obj !== null &&
46
+ 'type' in obj &&
47
+ obj.type === 'web_search_result' &&
48
+ 'url' in obj &&
49
+ typeof (obj as Record<string, unknown>).url === 'string'
50
+ );
51
+ }
@@ -1,173 +1,173 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- /* eslint-disable no-console */
3
- // content.test.ts
4
- import * as fs from 'fs';
5
- import { processContent } from './content';
6
-
7
- describe('Link Processor', () => {
8
- afterAll(() => {
9
- if (fs.existsSync('./temp.html')) {
10
- fs.unlinkSync('./temp.html');
11
- }
12
- if (fs.existsSync('./temp.md')) {
13
- fs.unlinkSync('./temp.md');
14
- }
15
- });
16
- // Basic functionality tests
17
- test('should replace basic links with references', () => {
18
- const html = `
19
- <p>Test with <a href="https://example.com/link" title="Example">a link</a></p>
20
- <p>And an <img src="https://example.com/img.jpg" alt="image"></p>
21
- <p>Plus a <video src="https://example.com/video.mp4"></video></p>
22
- `;
23
-
24
- const markdown = `
25
- Test with [a link](https://example.com/link "Example")
26
- And an ![image](https://example.com/img.jpg)
27
- Plus a [video](https://example.com/video.mp4)
28
- `;
29
-
30
- const result = processContent(html, markdown);
31
-
32
- expect(result.links.length).toBe(1);
33
- expect(result.images.length).toBe(1);
34
- expect(result.videos.length).toBe(1);
35
- expect(result.markdown).toContain('link#1');
36
- expect(result.markdown).toContain('image#1');
37
- expect(result.markdown).toContain('video#1');
38
- });
39
-
40
- // Edge case tests
41
- test('should handle links with parentheses and special characters', () => {
42
- const html = `
43
- <a href="https://example.com/page(1).html" title="Parens">Link with parens</a>
44
- <a href="https://example.com/path?query=test&param=value">Link with query</a>
45
- `;
46
-
47
- const markdown = `
48
- [Link with parens](https://example.com/page(1).html "Parens")
49
- [Link with query](https://example.com/path?query=test&param=value)
50
- `;
51
-
52
- const result = processContent(html, markdown);
53
-
54
- expect(result.links.length).toBe(2);
55
- expect(result.markdown).toContain('link#1');
56
- expect(result.markdown).toContain('link#2');
57
- });
58
-
59
- // Performance test with large files
60
- test('should process large files efficiently', () => {
61
- const html = fs.readFileSync('src/tools/search/test.html', 'utf-8');
62
- const markdown = fs.readFileSync('src/tools/search/test.md', 'utf-8');
63
-
64
- // const largeHtml = generateLargeHtml(1000); // 1000 links
65
- // fs.writeFileSync('./temp.html', largeHtml);
66
-
67
- // const largeMd = generateLargeMarkdown(1000); // 1000 links
68
- // fs.writeFileSync('./temp.md', largeMd);
69
-
70
- // const html = fs.readFileSync('./temp.html', 'utf-8');
71
- // const markdown = fs.readFileSync('./temp.md', 'utf-8');
72
-
73
- // Measure time taken to process
74
- const startTime = process.hrtime();
75
- const result = processContent(html, markdown);
76
- const elapsed = process.hrtime(startTime);
77
- const timeInMs = elapsed[0] * 1000 + elapsed[1] / 1000000;
78
-
79
- console.log(
80
- `Processed ${result.links.length} links, ${result.images.length} images, and ${result.videos.length} videos in ${timeInMs.toFixed(2)}ms`
81
- );
82
-
83
- // Basic validations for large file processing
84
- expect(result.links.length).toBeGreaterThan(0);
85
- expect(result.markdown).toContain('link#');
86
-
87
- // Check if all links were replaced (sample check)
88
- expect(result.markdown).not.toContain('https://example.com/link');
89
- });
90
-
91
- // Memory usage test
92
- test('should have reasonable memory usage', () => {
93
- const html = fs.readFileSync('src/tools/search/test.html', 'utf-8');
94
- const markdown = fs.readFileSync('src/tools/search/test.md', 'utf-8');
95
-
96
- const beforeMem = process.memoryUsage();
97
- processContent(html, markdown);
98
- const afterMem = process.memoryUsage();
99
-
100
- const heapUsed = (afterMem.heapUsed - beforeMem.heapUsed) / 1024 / 1024; // MB
101
-
102
- console.log(`Memory used: ${heapUsed.toFixed(2)} MB`);
103
-
104
- // This is a loose check - actual thresholds depend on your environment
105
- expect(heapUsed).toBeLessThan(100); // Should use less than 100MB additional heap
106
- });
107
-
108
- // Real-world file test (if available)
109
- test('should process real-world Wikipedia content', () => {
110
- // Try to find real-world test files if they exist
111
- const wikiHtml = 'src/tools/search/test.html';
112
- const wikiMd = 'src/tools/search/test.md';
113
-
114
- if (fs.existsSync(wikiHtml) && fs.existsSync(wikiMd)) {
115
- const html = fs.readFileSync(wikiHtml, 'utf-8');
116
- const markdown = fs.readFileSync(wikiMd, 'utf-8');
117
-
118
- const result = processContent(html, markdown);
119
-
120
- console.log(
121
- `Processed ${result.links.length} Wikipedia links, ${result.images.length} images, and ${result.videos.length} videos`
122
- );
123
-
124
- expect(result.links.length).toBeGreaterThan(10); // Wikipedia articles typically have many links
125
- expect(result.markdown).not.toMatch(/\]\(https?:\/\/[^\s")]+\)/); // No regular URLs should remain
126
- } else {
127
- console.log('Wikipedia test files not found, skipping this test');
128
- }
129
- });
130
- });
131
-
132
- // Helper function to generate large HTML test data
133
- function generateLargeHtml(linkCount: number): string {
134
- let html = '<html><body>';
135
-
136
- for (let i = 1; i <= linkCount; i++) {
137
- html += `<p>Paragraph ${i} with <a href="https://example.com/link${i}" title="Link ${i}">link ${i}</a>`;
138
-
139
- if (i % 10 === 0) {
140
- html += ` and <img src="https://example.com/image${i / 10}.jpg" alt="Image ${i / 10}">`;
141
- }
142
-
143
- if (i % 50 === 0) {
144
- html += ` and <video src="https://example.com/video${i / 50}.mp4" title="Video ${i / 50}"></video>`;
145
- }
146
-
147
- html += '</p>';
148
- }
149
-
150
- html += '</body></html>';
151
- return html;
152
- }
153
-
154
- /** Helper function to generate large Markdown test data */
155
- function generateLargeMarkdown(linkCount: number): string {
156
- let markdown = '# Test Document\n\n';
157
-
158
- for (let i = 1; i <= linkCount; i++) {
159
- markdown += `Paragraph ${i} with [link ${i}](https://example.com/link${i} "Link ${i}")`;
160
-
161
- if (i % 10 === 0) {
162
- markdown += ` and ![Image ${i / 10}](https://example.com/image${i / 10}.jpg)`;
163
- }
164
-
165
- if (i % 50 === 0) {
166
- markdown += ` and [Video ${i / 50}](https://example.com/video${i / 50}.mp4 "Video ${i / 50}")`;
167
- }
168
-
169
- markdown += '\n\n';
170
- }
171
-
172
- return markdown;
173
- }
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable no-console */
3
+ // content.test.ts
4
+ import * as fs from 'fs';
5
+ import { processContent } from './content';
6
+
7
+ describe('Link Processor', () => {
8
+ afterAll(() => {
9
+ if (fs.existsSync('./temp.html')) {
10
+ fs.unlinkSync('./temp.html');
11
+ }
12
+ if (fs.existsSync('./temp.md')) {
13
+ fs.unlinkSync('./temp.md');
14
+ }
15
+ });
16
+ // Basic functionality tests
17
+ test('should replace basic links with references', () => {
18
+ const html = `
19
+ <p>Test with <a href="https://example.com/link" title="Example">a link</a></p>
20
+ <p>And an <img src="https://example.com/img.jpg" alt="image"></p>
21
+ <p>Plus a <video src="https://example.com/video.mp4"></video></p>
22
+ `;
23
+
24
+ const markdown = `
25
+ Test with [a link](https://example.com/link "Example")
26
+ And an ![image](https://example.com/img.jpg)
27
+ Plus a [video](https://example.com/video.mp4)
28
+ `;
29
+
30
+ const result = processContent(html, markdown);
31
+
32
+ expect(result.links.length).toBe(1);
33
+ expect(result.images.length).toBe(1);
34
+ expect(result.videos.length).toBe(1);
35
+ expect(result.markdown).toContain('link#1');
36
+ expect(result.markdown).toContain('image#1');
37
+ expect(result.markdown).toContain('video#1');
38
+ });
39
+
40
+ // Edge case tests
41
+ test('should handle links with parentheses and special characters', () => {
42
+ const html = `
43
+ <a href="https://example.com/page(1).html" title="Parens">Link with parens</a>
44
+ <a href="https://example.com/path?query=test&param=value">Link with query</a>
45
+ `;
46
+
47
+ const markdown = `
48
+ [Link with parens](https://example.com/page(1).html "Parens")
49
+ [Link with query](https://example.com/path?query=test&param=value)
50
+ `;
51
+
52
+ const result = processContent(html, markdown);
53
+
54
+ expect(result.links.length).toBe(2);
55
+ expect(result.markdown).toContain('link#1');
56
+ expect(result.markdown).toContain('link#2');
57
+ });
58
+
59
+ // Performance test with large files
60
+ test('should process large files efficiently', () => {
61
+ const html = fs.readFileSync('src/tools/search/test.html', 'utf-8');
62
+ const markdown = fs.readFileSync('src/tools/search/test.md', 'utf-8');
63
+
64
+ // const largeHtml = generateLargeHtml(1000); // 1000 links
65
+ // fs.writeFileSync('./temp.html', largeHtml);
66
+
67
+ // const largeMd = generateLargeMarkdown(1000); // 1000 links
68
+ // fs.writeFileSync('./temp.md', largeMd);
69
+
70
+ // const html = fs.readFileSync('./temp.html', 'utf-8');
71
+ // const markdown = fs.readFileSync('./temp.md', 'utf-8');
72
+
73
+ // Measure time taken to process
74
+ const startTime = process.hrtime();
75
+ const result = processContent(html, markdown);
76
+ const elapsed = process.hrtime(startTime);
77
+ const timeInMs = elapsed[0] * 1000 + elapsed[1] / 1000000;
78
+
79
+ console.log(
80
+ `Processed ${result.links.length} links, ${result.images.length} images, and ${result.videos.length} videos in ${timeInMs.toFixed(2)}ms`
81
+ );
82
+
83
+ // Basic validations for large file processing
84
+ expect(result.links.length).toBeGreaterThan(0);
85
+ expect(result.markdown).toContain('link#');
86
+
87
+ // Check if all links were replaced (sample check)
88
+ expect(result.markdown).not.toContain('https://example.com/link');
89
+ });
90
+
91
+ // Memory usage test
92
+ test('should have reasonable memory usage', () => {
93
+ const html = fs.readFileSync('src/tools/search/test.html', 'utf-8');
94
+ const markdown = fs.readFileSync('src/tools/search/test.md', 'utf-8');
95
+
96
+ const beforeMem = process.memoryUsage();
97
+ processContent(html, markdown);
98
+ const afterMem = process.memoryUsage();
99
+
100
+ const heapUsed = (afterMem.heapUsed - beforeMem.heapUsed) / 1024 / 1024; // MB
101
+
102
+ console.log(`Memory used: ${heapUsed.toFixed(2)} MB`);
103
+
104
+ // This is a loose check - actual thresholds depend on your environment
105
+ expect(heapUsed).toBeLessThan(100); // Should use less than 100MB additional heap
106
+ });
107
+
108
+ // Real-world file test (if available)
109
+ test('should process real-world Wikipedia content', () => {
110
+ // Try to find real-world test files if they exist
111
+ const wikiHtml = 'src/tools/search/test.html';
112
+ const wikiMd = 'src/tools/search/test.md';
113
+
114
+ if (fs.existsSync(wikiHtml) && fs.existsSync(wikiMd)) {
115
+ const html = fs.readFileSync(wikiHtml, 'utf-8');
116
+ const markdown = fs.readFileSync(wikiMd, 'utf-8');
117
+
118
+ const result = processContent(html, markdown);
119
+
120
+ console.log(
121
+ `Processed ${result.links.length} Wikipedia links, ${result.images.length} images, and ${result.videos.length} videos`
122
+ );
123
+
124
+ expect(result.links.length).toBeGreaterThan(10); // Wikipedia articles typically have many links
125
+ expect(result.markdown).not.toMatch(/\]\(https?:\/\/[^\s")]+\)/); // No regular URLs should remain
126
+ } else {
127
+ console.log('Wikipedia test files not found, skipping this test');
128
+ }
129
+ });
130
+ });
131
+
132
+ // Helper function to generate large HTML test data
133
+ function generateLargeHtml(linkCount: number): string {
134
+ let html = '<html><body>';
135
+
136
+ for (let i = 1; i <= linkCount; i++) {
137
+ html += `<p>Paragraph ${i} with <a href="https://example.com/link${i}" title="Link ${i}">link ${i}</a>`;
138
+
139
+ if (i % 10 === 0) {
140
+ html += ` and <img src="https://example.com/image${i / 10}.jpg" alt="Image ${i / 10}">`;
141
+ }
142
+
143
+ if (i % 50 === 0) {
144
+ html += ` and <video src="https://example.com/video${i / 50}.mp4" title="Video ${i / 50}"></video>`;
145
+ }
146
+
147
+ html += '</p>';
148
+ }
149
+
150
+ html += '</body></html>';
151
+ return html;
152
+ }
153
+
154
+ /** Helper function to generate large Markdown test data */
155
+ function generateLargeMarkdown(linkCount: number): string {
156
+ let markdown = '# Test Document\n\n';
157
+
158
+ for (let i = 1; i <= linkCount; i++) {
159
+ markdown += `Paragraph ${i} with [link ${i}](https://example.com/link${i} "Link ${i}")`;
160
+
161
+ if (i % 10 === 0) {
162
+ markdown += ` and ![Image ${i / 10}](https://example.com/image${i / 10}.jpg)`;
163
+ }
164
+
165
+ if (i % 50 === 0) {
166
+ markdown += ` and [Video ${i / 50}](https://example.com/video${i / 50}.mp4 "Video ${i / 50}")`;
167
+ }
168
+
169
+ markdown += '\n\n';
170
+ }
171
+
172
+ return markdown;
173
+ }