@librechat/agents 2.4.52 → 2.4.53

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 (64) hide show
  1. package/dist/cjs/graphs/Graph.cjs +7 -6
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/index.cjs +8 -8
  4. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  6. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +15 -0
  7. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  8. package/dist/cjs/main.cjs +2 -0
  9. package/dist/cjs/main.cjs.map +1 -1
  10. package/dist/cjs/stream.cjs +8 -0
  11. package/dist/cjs/stream.cjs.map +1 -1
  12. package/dist/cjs/tools/ToolNode.cjs +8 -2
  13. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  14. package/dist/cjs/tools/handlers.cjs +101 -1
  15. package/dist/cjs/tools/handlers.cjs.map +1 -1
  16. package/dist/cjs/tools/search/anthropic.cjs +40 -0
  17. package/dist/cjs/tools/search/anthropic.cjs.map +1 -0
  18. package/dist/cjs/tools/search/search.cjs +2 -2
  19. package/dist/cjs/tools/search/search.cjs.map +1 -1
  20. package/dist/esm/graphs/Graph.mjs +7 -6
  21. package/dist/esm/graphs/Graph.mjs.map +1 -1
  22. package/dist/esm/llm/anthropic/index.mjs +8 -8
  23. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  24. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  25. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +15 -0
  26. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  27. package/dist/esm/main.mjs +1 -1
  28. package/dist/esm/stream.mjs +9 -1
  29. package/dist/esm/stream.mjs.map +1 -1
  30. package/dist/esm/tools/ToolNode.mjs +8 -2
  31. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  32. package/dist/esm/tools/handlers.mjs +101 -3
  33. package/dist/esm/tools/handlers.mjs.map +1 -1
  34. package/dist/esm/tools/search/anthropic.mjs +37 -0
  35. package/dist/esm/tools/search/anthropic.mjs.map +1 -0
  36. package/dist/esm/tools/search/search.mjs +2 -2
  37. package/dist/esm/tools/search/search.mjs.map +1 -1
  38. package/dist/types/graphs/Graph.d.ts +2 -0
  39. package/dist/types/llm/anthropic/types.d.ts +2 -0
  40. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
  41. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +3 -3
  42. package/dist/types/scripts/ant_web_search.d.ts +1 -0
  43. package/dist/types/tools/CodeExecutor.d.ts +2 -2
  44. package/dist/types/tools/ToolNode.d.ts +1 -1
  45. package/dist/types/tools/handlers.d.ts +11 -0
  46. package/dist/types/tools/search/anthropic.d.ts +16 -0
  47. package/dist/types/types/llm.d.ts +3 -2
  48. package/dist/types/types/stream.d.ts +9 -1
  49. package/package.json +5 -3
  50. package/src/graphs/Graph.ts +8 -6
  51. package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
  52. package/src/llm/anthropic/index.ts +7 -12
  53. package/src/llm/anthropic/llm.spec.ts +447 -115
  54. package/src/llm/anthropic/types.ts +16 -0
  55. package/src/llm/anthropic/utils/message_inputs.ts +17 -2
  56. package/src/llm/anthropic/utils/output_parsers.ts +4 -4
  57. package/src/scripts/ant_web_search.ts +158 -0
  58. package/src/stream.ts +16 -5
  59. package/src/tools/ToolNode.ts +17 -3
  60. package/src/tools/handlers.ts +163 -1
  61. package/src/tools/search/anthropic.ts +51 -0
  62. package/src/tools/search/search.ts +2 -1
  63. package/src/types/llm.ts +4 -2
  64. package/src/types/stream.ts +14 -0
@@ -49,6 +49,8 @@ export type AnthropicWebSearchToolResultBlockParam =
49
49
  Anthropic.Messages.WebSearchToolResultBlockParam;
50
50
  export type AnthropicWebSearchResultBlockParam =
51
51
  Anthropic.Messages.WebSearchResultBlockParam;
52
+ export type AnthropicSearchResultBlockParam =
53
+ Anthropic.Beta.BetaSearchResultBlockParam;
52
54
 
53
55
  // Union of all possible content block types including server tool use
54
56
  export type AnthropicContentBlock =
@@ -63,6 +65,20 @@ export type AnthropicContentBlock =
63
65
  | AnthropicWebSearchToolResultBlockParam
64
66
  | AnthropicWebSearchResultBlockParam;
65
67
 
68
+ // Union of all possible content block types including server tool use
69
+ export type ChatAnthropicContentBlock =
70
+ | AnthropicTextBlockParam
71
+ | AnthropicImageBlockParam
72
+ | AnthropicToolUseBlockParam
73
+ | AnthropicToolResultBlockParam
74
+ | AnthropicDocumentBlockParam
75
+ | AnthropicThinkingBlockParam
76
+ | AnthropicRedactedThinkingBlockParam
77
+ | AnthropicServerToolUseBlockParam
78
+ | AnthropicWebSearchToolResultBlockParam
79
+ | AnthropicWebSearchResultBlockParam
80
+ | AnthropicSearchResultBlockParam;
81
+
66
82
  export function isAnthropicImageBlockParam(
67
83
  block: unknown
68
84
  ): block is AnthropicImageBlockParam {
@@ -25,7 +25,6 @@ import {
25
25
  AnthropicImageBlockParam,
26
26
  AnthropicMessageCreateParams,
27
27
  AnthropicTextBlockParam,
28
- AnthropicToolResponse,
29
28
  AnthropicToolResultBlockParam,
30
29
  AnthropicToolUseBlockParam,
31
30
  AnthropicDocumentBlockParam,
@@ -34,7 +33,9 @@ import {
34
33
  AnthropicServerToolUseBlockParam,
35
34
  AnthropicWebSearchToolResultBlockParam,
36
35
  isAnthropicImageBlockParam,
37
- } from '@/llm/anthropic/types';
36
+ AnthropicSearchResultBlockParam,
37
+ AnthropicToolResponse,
38
+ } from '../types';
38
39
 
39
40
  function _formatImage(imageUrl: string) {
40
41
  const parsed = parseBase64DataUrl({ dataUrl: imageUrl });
@@ -407,6 +408,20 @@ function _formatContent(content: MessageContent) {
407
408
  ...(cacheControl ? { cache_control: cacheControl } : {}),
408
409
  };
409
410
  return block;
411
+ } else if (contentPart.type === 'search_result') {
412
+ const block: AnthropicSearchResultBlockParam = {
413
+ type: 'search_result' as const, // Explicitly setting the type as "search_result"
414
+ title: contentPart.title,
415
+ source: contentPart.source,
416
+ ...('cache_control' in contentPart && contentPart.cache_control
417
+ ? { cache_control: contentPart.cache_control }
418
+ : {}),
419
+ ...('citations' in contentPart && contentPart.citations
420
+ ? { citations: contentPart.citations }
421
+ : {}),
422
+ content: contentPart.content,
423
+ };
424
+ return block;
410
425
  } else if (
411
426
  textTypes.find((t) => t === contentPart.type) &&
412
427
  'text' in contentPart
@@ -4,17 +4,17 @@ import {
4
4
  BaseLLMOutputParser,
5
5
  OutputParserException,
6
6
  } from '@langchain/core/output_parsers';
7
- import { JsonOutputKeyToolsParserParams } from '@langchain/core/output_parsers/openai_tools';
7
+ import { JsonOutputKeyToolsParserParamsInterop } from '@langchain/core/output_parsers/openai_tools';
8
+ import { ChatGeneration } from '@langchain/core/outputs';
9
+ import { ToolCall } from '@langchain/core/messages/tool';
8
10
  import {
9
11
  interopSafeParseAsync,
10
12
  InteropZodType,
11
13
  } from '@langchain/core/utils/types';
12
- import { ChatGeneration } from '@langchain/core/outputs';
13
- import { ToolCall } from '@langchain/core/messages/tool';
14
14
 
15
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
16
  interface AnthropicToolsOutputParserParams<T extends Record<string, any>>
17
- extends JsonOutputKeyToolsParserParams<T> {}
17
+ extends JsonOutputKeyToolsParserParamsInterop<T> {}
18
18
 
19
19
  export class AnthropicToolsOutputParser<
20
20
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -0,0 +1,158 @@
1
+ /* eslint-disable no-console */
2
+ // src/scripts/cli.ts
3
+ import { config } from 'dotenv';
4
+ config();
5
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
6
+ import type * as t from '@/types';
7
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
8
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
9
+
10
+ import { getArgs } from '@/scripts/args';
11
+ import { Run } from '@/run';
12
+ import { GraphEvents, Callback, Providers } from '@/common';
13
+ import { getLLMConfig } from '@/utils/llmConfig';
14
+
15
+ const conversationHistory: BaseMessage[] = [];
16
+ let _contentParts: (t.MessageContentComplex | undefined)[] = [];
17
+ async function testStandardStreaming(): Promise<void> {
18
+ const { userName, location, currentDate } = await getArgs();
19
+ const { contentParts, aggregateContent } = createContentAggregator();
20
+ _contentParts = contentParts;
21
+ const customHandlers = {
22
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
23
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
24
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
25
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
26
+ handle: (
27
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
28
+ data: t.StreamEventData
29
+ ): void => {
30
+ console.log('====== ON_RUN_STEP_COMPLETED ======');
31
+ // console.dir(data, { depth: null });
32
+ aggregateContent({
33
+ event,
34
+ data: data as unknown as { result: t.ToolEndEvent },
35
+ });
36
+ },
37
+ },
38
+ [GraphEvents.ON_RUN_STEP]: {
39
+ handle: (
40
+ event: GraphEvents.ON_RUN_STEP,
41
+ data: t.StreamEventData
42
+ ): void => {
43
+ console.log('====== ON_RUN_STEP ======');
44
+ console.dir(data, { depth: null });
45
+ aggregateContent({ event, data: data as t.RunStep });
46
+ },
47
+ },
48
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
49
+ handle: (
50
+ event: GraphEvents.ON_RUN_STEP_DELTA,
51
+ data: t.StreamEventData
52
+ ): void => {
53
+ console.log('====== ON_RUN_STEP_DELTA ======');
54
+ console.dir(data, { depth: null });
55
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
56
+ },
57
+ },
58
+ [GraphEvents.ON_MESSAGE_DELTA]: {
59
+ handle: (
60
+ event: GraphEvents.ON_MESSAGE_DELTA,
61
+ data: t.StreamEventData
62
+ ): void => {
63
+ console.log('====== ON_MESSAGE_DELTA ======');
64
+ console.dir(data, { depth: null });
65
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
66
+ },
67
+ },
68
+ [GraphEvents.TOOL_START]: {
69
+ handle: (
70
+ _event: string,
71
+ data: t.StreamEventData,
72
+ metadata?: Record<string, unknown>
73
+ ): void => {
74
+ console.log('====== TOOL_START ======');
75
+ // console.dir(data, { depth: null });
76
+ },
77
+ },
78
+ };
79
+
80
+ const llmConfig = getLLMConfig(
81
+ Providers.ANTHROPIC
82
+ ) as t.AnthropicClientOptions & t.SharedLLMConfig;
83
+ llmConfig.model = 'claude-3-5-sonnet-latest';
84
+
85
+ const run = await Run.create<t.IState>({
86
+ runId: 'test-run-id',
87
+ graphConfig: {
88
+ type: 'standard',
89
+ llmConfig,
90
+ tools: [
91
+ {
92
+ type: 'web_search_20250305',
93
+ name: 'web_search',
94
+ max_uses: 5,
95
+ },
96
+ ],
97
+ instructions: 'You are a friendly AI assistant.',
98
+ // additional_instructions: `Always address the user by their name. The user's name is ${userName} and they are located in ${location}.`,
99
+ },
100
+ returnContent: true,
101
+ customHandlers,
102
+ });
103
+
104
+ const config = {
105
+ configurable: {
106
+ provider: Providers.ANTHROPIC,
107
+ thread_id: 'conversation-num-1',
108
+ },
109
+ streamMode: 'values',
110
+ version: 'v2' as const,
111
+ };
112
+
113
+ console.log('Test 1: Search query (search tool test)');
114
+
115
+ // const userMessage = `
116
+ // Make a search for the weather in ${location} today, which is ${currentDate}.
117
+ // Before making the search, please let me know what you're about to do, then immediately start searching without hesitation.
118
+ // Make sure to always refer to me by name, which is ${userName}.
119
+ // After giving me a thorough summary, tell me a joke about the weather forecast we went over.
120
+ // `;
121
+ // const userMessage = 'Are massage guns good?';
122
+ // const userMessage = 'What is functional programming?';
123
+ const userMessage = "Get me today's trending news.";
124
+ // const userMessage = "search recent italy earthquake volcano activity";
125
+ // const userMessage =
126
+ // "use 'Trump' as the exact search query and tell me what you find.";
127
+
128
+ conversationHistory.push(new HumanMessage(userMessage));
129
+
130
+ const inputs = {
131
+ messages: conversationHistory,
132
+ };
133
+ const finalContentParts = await run.processStream(inputs, config);
134
+ const finalMessages = run.getRunMessages();
135
+ if (finalMessages) {
136
+ conversationHistory.push(...finalMessages);
137
+ console.dir(conversationHistory, { depth: null });
138
+ }
139
+ // console.dir(finalContentParts, { depth: null });
140
+ console.log('\n\n====================\n\n');
141
+ // console.dir(contentParts, { depth: null });
142
+ }
143
+
144
+ process.on('unhandledRejection', (reason, promise) => {
145
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
146
+ console.log('Content Parts:');
147
+ console.dir(_contentParts, { depth: null });
148
+ process.exit(1);
149
+ });
150
+
151
+ testStandardStreaming().catch((err) => {
152
+ console.error(err);
153
+ console.log('Conversation history:');
154
+ console.dir(conversationHistory, { depth: null });
155
+ console.log('Content Parts:');
156
+ console.dir(_contentParts, { depth: null });
157
+ process.exit(1);
158
+ });
package/src/stream.ts CHANGED
@@ -1,17 +1,21 @@
1
1
  // src/stream.ts
2
+ import type { ChatOpenAIReasoningSummary } from '@langchain/openai';
2
3
  import type { AIMessageChunk } from '@langchain/core/messages';
3
4
  import type { ToolCall } from '@langchain/core/messages/tool';
4
5
  import type { Graph } from '@/graphs';
5
- import type { ChatOpenAIReasoningSummary } from '@langchain/openai';
6
6
  import type * as t from '@/types';
7
7
  import {
8
- StepTypes,
8
+ ToolCallTypes,
9
9
  ContentTypes,
10
10
  GraphEvents,
11
- ToolCallTypes,
11
+ StepTypes,
12
12
  Providers,
13
13
  } from '@/common';
14
- import { handleToolCalls, handleToolCallChunks } from '@/tools/handlers';
14
+ import {
15
+ handleServerToolResult,
16
+ handleToolCallChunks,
17
+ handleToolCalls,
18
+ } from '@/tools/handlers';
15
19
  import { getMessageId } from '@/messages';
16
20
 
17
21
  /**
@@ -132,8 +136,15 @@ export class ChatModelStreamHandler implements t.EventHandler {
132
136
  reasoningKey: graph.reasoningKey,
133
137
  provider: metadata?.provider as Providers,
134
138
  });
139
+ const skipHandling = handleServerToolResult({
140
+ content,
141
+ metadata,
142
+ graph,
143
+ });
144
+ if (skipHandling) {
145
+ return;
146
+ }
135
147
  this.handleReasoning(chunk, graph, metadata?.provider as Providers);
136
-
137
148
  let hasToolCalls = false;
138
149
  if (
139
150
  chunk.tool_calls &&
@@ -138,16 +138,30 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
138
138
  }
139
139
  }
140
140
 
141
+ function areToolCallsInvoked(
142
+ message: AIMessage,
143
+ invokedToolIds?: Set<string>
144
+ ): boolean {
145
+ if (!invokedToolIds || invokedToolIds.size === 0) return false;
146
+ return (
147
+ message.tool_calls?.every(
148
+ (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)
149
+ ) ?? false
150
+ );
151
+ }
152
+
141
153
  export function toolsCondition(
142
- state: BaseMessage[] | typeof MessagesAnnotation.State
154
+ state: BaseMessage[] | typeof MessagesAnnotation.State,
155
+ invokedToolIds?: Set<string>
143
156
  ): 'tools' | typeof END {
144
- const message = Array.isArray(state)
157
+ const message: AIMessage = Array.isArray(state)
145
158
  ? state[state.length - 1]
146
159
  : state.messages[state.messages.length - 1];
147
160
 
148
161
  if (
149
162
  'tool_calls' in message &&
150
- ((message as AIMessage).tool_calls?.length ?? 0) > 0
163
+ (message.tool_calls?.length ?? 0) > 0 &&
164
+ !areToolCallsInvoked(message, invokedToolIds)
151
165
  ) {
152
166
  return GraphNodeKeys.TOOLS;
153
167
  } else {
@@ -1,10 +1,22 @@
1
1
  /* eslint-disable no-console */
2
2
  // src/tools/handlers.ts
3
3
  import { nanoid } from 'nanoid';
4
+ import { ToolMessage } from '@langchain/core/messages';
5
+ import type { AnthropicWebSearchResultBlockParam } from '@/llm/anthropic/types';
4
6
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
5
7
  import type { Graph } from '@/graphs';
6
8
  import type * as t from '@/types';
7
- import { StepTypes, ContentTypes, ToolCallTypes } from '@/common';
9
+ import {
10
+ coerceAnthropicSearchResults,
11
+ isAnthropicWebSearchResult,
12
+ } from '@/tools/search/anthropic';
13
+ import {
14
+ StepTypes,
15
+ ContentTypes,
16
+ ToolCallTypes,
17
+ Providers,
18
+ Constants,
19
+ } from '@/common';
8
20
  import { getMessageId } from '@/messages';
9
21
 
10
22
  export function handleToolCallChunks({
@@ -165,3 +177,153 @@ export const handleToolCalls = (
165
177
  });
166
178
  }
167
179
  };
180
+
181
+ export const toolResultTypes = new Set([
182
+ // 'tool_use',
183
+ // 'server_tool_use',
184
+ // 'input_json_delta',
185
+ 'tool_result',
186
+ 'web_search_result',
187
+ 'web_search_tool_result',
188
+ ]);
189
+
190
+ /**
191
+ * Handles the result of a server tool call; in other words, a provider's built-in tool.
192
+ * As of 2025-07-06, only Anthropic handles server tool calls with this pattern.
193
+ */
194
+ export function handleServerToolResult({
195
+ content,
196
+ metadata,
197
+ graph,
198
+ }: {
199
+ content?: string | t.MessageContentComplex[];
200
+ metadata?: Record<string, unknown>;
201
+ graph: Graph;
202
+ }): boolean {
203
+ let skipHandling = false;
204
+ if (metadata?.provider !== Providers.ANTHROPIC) {
205
+ return skipHandling;
206
+ }
207
+ if (
208
+ typeof content === 'string' ||
209
+ content == null ||
210
+ content.length === 0 ||
211
+ (content.length === 1 &&
212
+ (content[0] as t.ToolResultContent).tool_use_id == null)
213
+ ) {
214
+ return skipHandling;
215
+ }
216
+
217
+ for (const contentPart of content) {
218
+ const toolUseId = (contentPart as t.ToolResultContent).tool_use_id;
219
+ if (toolUseId == null || toolUseId === '') {
220
+ continue;
221
+ }
222
+ const stepId = graph.toolCallStepIds.get(toolUseId);
223
+ if (stepId == null || stepId === '') {
224
+ console.warn(
225
+ `Tool use ID ${toolUseId} not found in graph, cannot dispatch tool result.`
226
+ );
227
+ continue;
228
+ }
229
+ const runStep = graph.getRunStep(stepId);
230
+ if (!runStep) {
231
+ console.warn(
232
+ `Run step for ${stepId} does not exist, cannot dispatch tool result.`
233
+ );
234
+ continue;
235
+ } else if (runStep.type !== StepTypes.TOOL_CALLS) {
236
+ console.warn(
237
+ `Run step for ${stepId} is not a tool call step, cannot dispatch tool result.`
238
+ );
239
+ continue;
240
+ }
241
+
242
+ const toolCall =
243
+ runStep.stepDetails.type === StepTypes.TOOL_CALLS
244
+ ? (runStep.stepDetails.tool_calls?.find(
245
+ (toolCall) => toolCall.id === toolUseId
246
+ ) as ToolCall)
247
+ : undefined;
248
+
249
+ if (!toolCall) {
250
+ continue;
251
+ }
252
+
253
+ if (
254
+ contentPart.type === 'web_search_result' ||
255
+ contentPart.type === 'web_search_tool_result'
256
+ ) {
257
+ handleAnthropicSearchResults({
258
+ contentPart: contentPart as t.ToolResultContent,
259
+ toolCall,
260
+ metadata,
261
+ graph,
262
+ });
263
+ }
264
+
265
+ if (!skipHandling) {
266
+ skipHandling = true;
267
+ }
268
+ }
269
+
270
+ return skipHandling;
271
+ }
272
+
273
+ function handleAnthropicSearchResults({
274
+ contentPart,
275
+ toolCall,
276
+ metadata,
277
+ graph,
278
+ }: {
279
+ contentPart: t.ToolResultContent;
280
+ toolCall: ToolCall;
281
+ metadata?: Record<string, unknown>;
282
+ graph: Graph;
283
+ }): void {
284
+ if (!Array.isArray(contentPart.content)) {
285
+ console.warn(
286
+ `Expected content to be an array, got ${typeof contentPart.content}`
287
+ );
288
+ return;
289
+ }
290
+
291
+ if (!isAnthropicWebSearchResult(contentPart.content[0])) {
292
+ console.warn(
293
+ `Expected content to be an Anthropic web search result, got ${JSON.stringify(
294
+ contentPart.content
295
+ )}`
296
+ );
297
+ return;
298
+ }
299
+
300
+ const searchResultData = coerceAnthropicSearchResults({
301
+ results: contentPart.content as AnthropicWebSearchResultBlockParam[],
302
+ turn: graph.invokedToolIds?.size,
303
+ });
304
+
305
+ const name = toolCall.name;
306
+ const input = toolCall.args;
307
+ const artifact = {
308
+ [Constants.WEB_SEARCH]: searchResultData,
309
+ };
310
+ const output = new ToolMessage({
311
+ name,
312
+ artifact,
313
+ content: 'Anthropic web search results',
314
+ tool_call_id: toolCall.id!,
315
+ });
316
+ graph.handleToolCallCompleted(
317
+ {
318
+ input,
319
+ output,
320
+ },
321
+ metadata
322
+ );
323
+
324
+ if (graph.invokedToolIds == null) {
325
+ graph.invokedToolIds = new Set<string>();
326
+ }
327
+
328
+ graph.invokedToolIds.add(toolCall.id!);
329
+ }
@@ -0,0 +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
+ }
@@ -233,6 +233,7 @@ const createSearXNGAPI = (
233
233
  const getSources = async ({
234
234
  query,
235
235
  numResults = 8,
236
+ safeSearch,
236
237
  type,
237
238
  }: t.GetSourcesParams): Promise<t.SearchResult> => {
238
239
  if (!query.trim()) {
@@ -267,7 +268,7 @@ const createSearXNGAPI = (
267
268
  pageno: 1,
268
269
  categories: category,
269
270
  language: 'all',
270
- safesearch: 0,
271
+ safesearch: safeSearch,
271
272
  engines: 'google,bing,duckduckgo',
272
273
  };
273
274
 
package/src/types/llm.ts CHANGED
@@ -91,9 +91,11 @@ export type ClientOptions =
91
91
  | DeepSeekClientOptions
92
92
  | XAIClientOptions;
93
93
 
94
- export type LLMConfig = {
94
+ export type SharedLLMConfig = {
95
95
  provider: Providers;
96
- } & ClientOptions;
96
+ };
97
+
98
+ export type LLMConfig = SharedLLMConfig & ClientOptions;
97
99
 
98
100
  export type ProviderOptionsMap = {
99
101
  [Providers.AZURE]: AzureClientOptions;
@@ -8,6 +8,7 @@ import type {
8
8
  } from '@langchain/core/messages';
9
9
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
10
10
  import type { LLMResult, Generation } from '@langchain/core/outputs';
11
+ import type { AnthropicContentBlock } from '@/llm/anthropic/types';
11
12
  import type { ToolEndEvent } from '@/types/tools';
12
13
  import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
13
14
 
@@ -299,7 +300,20 @@ export type ToolCallContent = {
299
300
  tool_call?: ToolCallPart;
300
301
  };
301
302
 
303
+ export type ToolResultContent = {
304
+ content:
305
+ | string
306
+ | Record<string, unknown>
307
+ | Array<string | Record<string, unknown>>
308
+ | AnthropicContentBlock[];
309
+ type: 'tool_result' | 'web_search_result' | 'web_search_tool_result';
310
+ tool_use_id?: string;
311
+ input?: string | Record<string, unknown>;
312
+ index?: number;
313
+ };
314
+
302
315
  export type MessageContentComplex = (
316
+ | ToolResultContent
303
317
  | ThinkingContentText
304
318
  | AgentUpdate
305
319
  | ToolCallContent