@librechat/agents 2.4.31 → 2.4.33

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 (115) hide show
  1. package/dist/cjs/events.cjs +3 -3
  2. package/dist/cjs/events.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +2 -1
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/main.cjs +5 -2
  6. package/dist/cjs/main.cjs.map +1 -1
  7. package/dist/cjs/messages/ids.cjs +23 -0
  8. package/dist/cjs/messages/ids.cjs.map +1 -0
  9. package/dist/cjs/splitStream.cjs +2 -1
  10. package/dist/cjs/splitStream.cjs.map +1 -1
  11. package/dist/cjs/stream.cjs +87 -154
  12. package/dist/cjs/stream.cjs.map +1 -1
  13. package/dist/cjs/tools/ToolNode.cjs +14 -3
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/cjs/tools/handlers.cjs +144 -0
  16. package/dist/cjs/tools/handlers.cjs.map +1 -0
  17. package/dist/cjs/tools/search/content.cjs +140 -0
  18. package/dist/cjs/tools/search/content.cjs.map +1 -0
  19. package/dist/cjs/tools/search/firecrawl.cjs +23 -41
  20. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  21. package/dist/cjs/tools/search/format.cjs +161 -74
  22. package/dist/cjs/tools/search/format.cjs.map +1 -1
  23. package/dist/cjs/tools/search/highlights.cjs +64 -12
  24. package/dist/cjs/tools/search/highlights.cjs.map +1 -1
  25. package/dist/cjs/tools/search/rerankers.cjs +43 -36
  26. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  27. package/dist/cjs/tools/search/schema.cjs +70 -0
  28. package/dist/cjs/tools/search/schema.cjs.map +1 -0
  29. package/dist/cjs/tools/search/search.cjs +150 -69
  30. package/dist/cjs/tools/search/search.cjs.map +1 -1
  31. package/dist/cjs/tools/search/tool.cjs +247 -58
  32. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  33. package/dist/cjs/tools/search/utils.cjs +66 -0
  34. package/dist/cjs/tools/search/utils.cjs.map +1 -0
  35. package/dist/esm/events.mjs +1 -1
  36. package/dist/esm/events.mjs.map +1 -1
  37. package/dist/esm/graphs/Graph.mjs +2 -1
  38. package/dist/esm/graphs/Graph.mjs.map +1 -1
  39. package/dist/esm/main.mjs +3 -1
  40. package/dist/esm/main.mjs.map +1 -1
  41. package/dist/esm/messages/ids.mjs +21 -0
  42. package/dist/esm/messages/ids.mjs.map +1 -0
  43. package/dist/esm/splitStream.mjs +2 -1
  44. package/dist/esm/splitStream.mjs.map +1 -1
  45. package/dist/esm/stream.mjs +87 -152
  46. package/dist/esm/stream.mjs.map +1 -1
  47. package/dist/esm/tools/ToolNode.mjs +14 -3
  48. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  49. package/dist/esm/tools/handlers.mjs +141 -0
  50. package/dist/esm/tools/handlers.mjs.map +1 -0
  51. package/dist/esm/tools/search/content.mjs +119 -0
  52. package/dist/esm/tools/search/content.mjs.map +1 -0
  53. package/dist/esm/tools/search/firecrawl.mjs +24 -41
  54. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  55. package/dist/esm/tools/search/format.mjs +161 -74
  56. package/dist/esm/tools/search/format.mjs.map +1 -1
  57. package/dist/esm/tools/search/highlights.mjs +64 -12
  58. package/dist/esm/tools/search/highlights.mjs.map +1 -1
  59. package/dist/esm/tools/search/rerankers.mjs +43 -36
  60. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  61. package/dist/esm/tools/search/schema.mjs +61 -0
  62. package/dist/esm/tools/search/schema.mjs.map +1 -0
  63. package/dist/esm/tools/search/search.mjs +150 -69
  64. package/dist/esm/tools/search/search.mjs.map +1 -1
  65. package/dist/esm/tools/search/tool.mjs +246 -57
  66. package/dist/esm/tools/search/tool.mjs.map +1 -1
  67. package/dist/esm/tools/search/utils.mjs +61 -0
  68. package/dist/esm/tools/search/utils.mjs.map +1 -0
  69. package/dist/types/graphs/Graph.d.ts +1 -1
  70. package/dist/types/index.d.ts +1 -0
  71. package/dist/types/messages/ids.d.ts +3 -0
  72. package/dist/types/messages/index.d.ts +1 -0
  73. package/dist/types/stream.d.ts +0 -8
  74. package/dist/types/tools/ToolNode.d.ts +6 -0
  75. package/dist/types/tools/example.d.ts +23 -3
  76. package/dist/types/tools/handlers.d.ts +8 -0
  77. package/dist/types/tools/search/content.d.ts +4 -0
  78. package/dist/types/tools/search/firecrawl.d.ts +7 -86
  79. package/dist/types/tools/search/format.d.ts +4 -1
  80. package/dist/types/tools/search/highlights.d.ts +1 -1
  81. package/dist/types/tools/search/rerankers.d.ts +8 -4
  82. package/dist/types/tools/search/schema.d.ts +16 -0
  83. package/dist/types/tools/search/search.d.ts +2 -2
  84. package/dist/types/tools/search/test.d.ts +1 -0
  85. package/dist/types/tools/search/tool.d.ts +25 -4
  86. package/dist/types/tools/search/types.d.ts +443 -53
  87. package/dist/types/tools/search/utils.d.ts +10 -0
  88. package/package.json +9 -7
  89. package/src/events.ts +49 -15
  90. package/src/graphs/Graph.ts +6 -2
  91. package/src/index.ts +1 -0
  92. package/src/messages/ids.ts +26 -0
  93. package/src/messages/index.ts +1 -0
  94. package/src/scripts/search.ts +8 -3
  95. package/src/splitStream.test.ts +132 -71
  96. package/src/splitStream.ts +2 -1
  97. package/src/stream.ts +94 -183
  98. package/src/tools/ToolNode.ts +37 -14
  99. package/src/tools/handlers.ts +167 -0
  100. package/src/tools/search/content.test.ts +173 -0
  101. package/src/tools/search/content.ts +147 -0
  102. package/src/tools/search/firecrawl.ts +36 -148
  103. package/src/tools/search/format.ts +205 -74
  104. package/src/tools/search/highlights.ts +99 -16
  105. package/src/tools/search/output.md +2775 -0
  106. package/src/tools/search/rerankers.ts +57 -36
  107. package/src/tools/search/schema.ts +63 -0
  108. package/src/tools/search/search.ts +230 -117
  109. package/src/tools/search/test.html +884 -0
  110. package/src/tools/search/test.md +643 -0
  111. package/src/tools/search/test.ts +159 -0
  112. package/src/tools/search/tool.ts +363 -87
  113. package/src/tools/search/types.ts +503 -61
  114. package/src/tools/search/utils.ts +79 -0
  115. package/src/utils/llmConfig.ts +1 -1
package/src/stream.ts CHANGED
@@ -1,120 +1,71 @@
1
1
  // src/stream.ts
2
- import { nanoid } from 'nanoid';
3
2
  import type { AIMessageChunk } from '@langchain/core/messages';
4
- import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
3
+ import type { ToolCall } from '@langchain/core/messages/tool';
5
4
  import type { Graph } from '@/graphs';
6
5
  import type * as t from '@/types';
7
6
  import { StepTypes, ContentTypes, GraphEvents, ToolCallTypes } from '@/common';
8
-
9
- function getNonEmptyValue(possibleValues: string[]): string | undefined {
10
- for (const value of possibleValues) {
11
- if (value && value.trim() !== '') {
12
- return value;
13
- }
7
+ import { handleToolCalls, handleToolCallChunks } from '@/tools/handlers';
8
+ import { getMessageId } from '@/messages';
9
+
10
+ /**
11
+ * Parses content to extract thinking sections enclosed in <think> tags using string operations
12
+ * @param content The content to parse
13
+ * @returns An object with separated text and thinking content
14
+ */
15
+ function parseThinkingContent(content: string): {
16
+ text: string;
17
+ thinking: string;
18
+ } {
19
+ // If no think tags, return the original content as text
20
+ if (!content.includes('<think>')) {
21
+ return { text: content, thinking: '' };
14
22
  }
15
- return undefined;
16
- }
17
23
 
18
- export const getMessageId = (
19
- stepKey: string,
20
- graph: Graph<t.BaseGraphState>,
21
- returnExistingId = false
22
- ): string | undefined => {
23
- const messageId = graph.messageIdsByStepKey.get(stepKey);
24
- if (messageId != null && messageId) {
25
- return returnExistingId ? messageId : undefined;
26
- }
24
+ let textResult = '';
25
+ const thinkingResult: string[] = [];
26
+ let position = 0;
27
27
 
28
- const prelimMessageId = graph.prelimMessageIdsByStepKey.get(stepKey);
29
- if (prelimMessageId != null && prelimMessageId) {
30
- graph.prelimMessageIdsByStepKey.delete(stepKey);
31
- graph.messageIdsByStepKey.set(stepKey, prelimMessageId);
32
- return prelimMessageId;
33
- }
28
+ while (position < content.length) {
29
+ const thinkStart = content.indexOf('<think>', position);
34
30
 
35
- const message_id = `msg_${nanoid()}`;
36
- graph.messageIdsByStepKey.set(stepKey, message_id);
37
- return message_id;
38
- };
39
-
40
- export const handleToolCalls = (
41
- toolCalls?: ToolCall[],
42
- metadata?: Record<string, unknown>,
43
- graph?: Graph
44
- ): void => {
45
- if (!graph || !metadata) {
46
- console.warn(`Graph or metadata not found in ${event} event`);
47
- return;
48
- }
31
+ if (thinkStart === -1) {
32
+ // No more think tags, add the rest and break
33
+ textResult += content.slice(position);
34
+ break;
35
+ }
49
36
 
50
- if (!toolCalls) {
51
- return;
52
- }
37
+ // Add text before the think tag
38
+ textResult += content.slice(position, thinkStart);
53
39
 
54
- if (toolCalls.length === 0) {
55
- return;
56
- }
40
+ const thinkEnd = content.indexOf('</think>', thinkStart);
41
+ if (thinkEnd === -1) {
42
+ // Malformed input, no closing tag
43
+ textResult += content.slice(thinkStart);
44
+ break;
45
+ }
57
46
 
58
- const stepKey = graph.getStepKey(metadata);
47
+ // Add the thinking content
48
+ const thinkContent = content.slice(thinkStart + 7, thinkEnd);
49
+ thinkingResult.push(thinkContent);
59
50
 
60
- for (const tool_call of toolCalls) {
61
- const toolCallId = tool_call.id ?? `toolu_${nanoid()}`;
62
- tool_call.id = toolCallId;
63
- if (!toolCallId || graph.toolCallStepIds.has(toolCallId)) {
64
- continue;
65
- }
51
+ // Move position to after the think tag
52
+ position = thinkEnd + 8; // 8 is the length of '</think>'
53
+ }
66
54
 
67
- let prevStepId = '';
68
- let prevRunStep: t.RunStep | undefined;
69
- try {
70
- prevStepId = graph.getStepIdByKey(stepKey, graph.contentData.length - 1);
71
- prevRunStep = graph.getRunStep(prevStepId);
72
- } catch {
73
- // no previous step
74
- }
55
+ return {
56
+ text: textResult.trim(),
57
+ thinking: thinkingResult.join('\n').trim(),
58
+ };
59
+ }
75
60
 
76
- const dispatchToolCallIds = (lastMessageStepId: string): void => {
77
- graph.dispatchMessageDelta(lastMessageStepId, {
78
- content: [
79
- {
80
- type: 'text',
81
- text: '',
82
- tool_call_ids: [toolCallId],
83
- },
84
- ],
85
- });
86
- };
87
- /* If the previous step exists and is a message creation */
88
- if (
89
- prevStepId &&
90
- prevRunStep &&
91
- prevRunStep.type === StepTypes.MESSAGE_CREATION
92
- ) {
93
- dispatchToolCallIds(prevStepId);
94
- graph.messageStepHasToolCalls.set(prevStepId, true);
95
- /* If the previous step doesn't exist or is not a message creation */
96
- } else if (
97
- !prevRunStep ||
98
- prevRunStep.type !== StepTypes.MESSAGE_CREATION
99
- ) {
100
- const messageId = getMessageId(stepKey, graph, true) ?? '';
101
- const stepId = graph.dispatchRunStep(stepKey, {
102
- type: StepTypes.MESSAGE_CREATION,
103
- message_creation: {
104
- message_id: messageId,
105
- },
106
- });
107
- dispatchToolCallIds(stepId);
108
- graph.messageStepHasToolCalls.set(prevStepId, true);
61
+ function getNonEmptyValue(possibleValues: string[]): string | undefined {
62
+ for (const value of possibleValues) {
63
+ if (value && value.trim() !== '') {
64
+ return value;
109
65
  }
110
-
111
- graph.dispatchRunStep(stepKey, {
112
- type: StepTypes.TOOL_CALLS,
113
- tool_calls: [tool_call],
114
- });
115
66
  }
116
- };
117
-
67
+ return undefined;
68
+ }
118
69
  export class ChatModelStreamHandler implements t.EventHandler {
119
70
  handle(
120
71
  event: string,
@@ -179,7 +130,7 @@ export class ChatModelStreamHandler implements t.EventHandler {
179
130
  chunk.tool_call_chunks.length &&
180
131
  typeof chunk.tool_call_chunks[0]?.index === 'number'
181
132
  ) {
182
- this.handleToolCallChunks({
133
+ handleToolCallChunks({
183
134
  graph,
184
135
  stepKey,
185
136
  toolCallChunks: chunk.tool_call_chunks,
@@ -239,6 +190,40 @@ hasToolCallChunks: ${hasToolCallChunks}
239
190
  },
240
191
  ],
241
192
  });
193
+ } else if (graph.currentTokenType === 'think_and_text') {
194
+ const { text, thinking } = parseThinkingContent(content);
195
+ if (thinking) {
196
+ graph.dispatchReasoningDelta(stepId, {
197
+ content: [
198
+ {
199
+ type: ContentTypes.THINK,
200
+ think: thinking,
201
+ },
202
+ ],
203
+ });
204
+ }
205
+ if (text) {
206
+ graph.currentTokenType = ContentTypes.TEXT;
207
+ graph.tokenTypeSwitch = 'content';
208
+ const newStepKey = graph.getStepKey(metadata);
209
+ const message_id = getMessageId(newStepKey, graph) ?? '';
210
+ graph.dispatchRunStep(newStepKey, {
211
+ type: StepTypes.MESSAGE_CREATION,
212
+ message_creation: {
213
+ message_id,
214
+ },
215
+ });
216
+
217
+ const newStepId = graph.getStepIdByKey(newStepKey);
218
+ graph.dispatchMessageDelta(newStepId, {
219
+ content: [
220
+ {
221
+ type: ContentTypes.TEXT,
222
+ text: text,
223
+ },
224
+ ],
225
+ });
226
+ }
242
227
  } else {
243
228
  graph.dispatchReasoningDelta(stepId, {
244
229
  content: [
@@ -273,88 +258,6 @@ hasToolCallChunks: ${hasToolCallChunks}
273
258
  });
274
259
  }
275
260
  }
276
- handleToolCallChunks = ({
277
- graph,
278
- stepKey,
279
- toolCallChunks,
280
- }: {
281
- graph: Graph;
282
- stepKey: string;
283
- toolCallChunks: ToolCallChunk[];
284
- }): void => {
285
- let prevStepId: string;
286
- let prevRunStep: t.RunStep | undefined;
287
- try {
288
- prevStepId = graph.getStepIdByKey(stepKey, graph.contentData.length - 1);
289
- prevRunStep = graph.getRunStep(prevStepId);
290
- } catch {
291
- /** Edge Case: If no previous step exists, create a new message creation step */
292
- const message_id = getMessageId(stepKey, graph, true) ?? '';
293
- prevStepId = graph.dispatchRunStep(stepKey, {
294
- type: StepTypes.MESSAGE_CREATION,
295
- message_creation: {
296
- message_id,
297
- },
298
- });
299
- prevRunStep = graph.getRunStep(prevStepId);
300
- }
301
-
302
- const _stepId = graph.getStepIdByKey(stepKey, prevRunStep?.index);
303
-
304
- /** Edge Case: Tool Call Run Step or `tool_call_ids` never dispatched */
305
- const tool_calls: ToolCall[] | undefined =
306
- prevStepId &&
307
- prevRunStep &&
308
- prevRunStep.type === StepTypes.MESSAGE_CREATION
309
- ? []
310
- : undefined;
311
-
312
- /** Edge Case: `id` and `name` fields cannot be empty strings */
313
- for (const toolCallChunk of toolCallChunks) {
314
- if (toolCallChunk.name === '') {
315
- toolCallChunk.name = undefined;
316
- }
317
- if (toolCallChunk.id === '') {
318
- toolCallChunk.id = undefined;
319
- } else if (
320
- tool_calls != null &&
321
- toolCallChunk.id != null &&
322
- toolCallChunk.name != null
323
- ) {
324
- tool_calls.push({
325
- args: {},
326
- id: toolCallChunk.id,
327
- name: toolCallChunk.name,
328
- type: ToolCallTypes.TOOL_CALL,
329
- });
330
- }
331
- }
332
-
333
- let stepId: string = _stepId;
334
- const alreadyDispatched =
335
- prevRunStep?.type === StepTypes.MESSAGE_CREATION &&
336
- graph.messageStepHasToolCalls.has(prevStepId);
337
- if (!alreadyDispatched && tool_calls?.length === toolCallChunks.length) {
338
- graph.dispatchMessageDelta(prevStepId, {
339
- content: [
340
- {
341
- type: ContentTypes.TEXT,
342
- text: '',
343
- tool_call_ids: tool_calls.map((tc) => tc.id ?? ''),
344
- },
345
- ],
346
- });
347
- graph.messageStepHasToolCalls.set(prevStepId, true);
348
- stepId = graph.dispatchRunStep(stepKey, {
349
- type: StepTypes.TOOL_CALLS,
350
- tool_calls,
351
- });
352
- }
353
- graph.dispatchRunStepDelta(stepId, {
354
- type: StepTypes.TOOL_CALLS,
355
- tool_calls: toolCallChunks,
356
- });
357
- };
358
261
  handleReasoning(chunk: Partial<AIMessageChunk>, graph: Graph): void {
359
262
  let reasoning_content = chunk.additional_kwargs?.[graph.reasoningKey] as
360
263
  | string
@@ -384,6 +287,14 @@ hasToolCallChunks: ${hasToolCallChunks}
384
287
  ) {
385
288
  graph.currentTokenType = ContentTypes.TEXT;
386
289
  graph.tokenTypeSwitch = 'content';
290
+ } else if (
291
+ chunk.content != null &&
292
+ typeof chunk.content === 'string' &&
293
+ chunk.content.includes('<think>') &&
294
+ chunk.content.includes('</think>')
295
+ ) {
296
+ graph.currentTokenType = 'think_and_text';
297
+ graph.tokenTypeSwitch = 'content';
387
298
  } else if (
388
299
  chunk.content != null &&
389
300
  typeof chunk.content === 'string' &&
@@ -1,10 +1,18 @@
1
- import { END, MessagesAnnotation, isCommand, isGraphInterrupt } from '@langchain/langgraph';
1
+ import {
2
+ END,
3
+ MessagesAnnotation,
4
+ isCommand,
5
+ isGraphInterrupt,
6
+ } from '@langchain/langgraph';
2
7
  import { ToolMessage, isBaseMessage } from '@langchain/core/messages';
3
- import type { RunnableConfig, RunnableToolLike } from '@langchain/core/runnables';
8
+ import type {
9
+ RunnableConfig,
10
+ RunnableToolLike,
11
+ } from '@langchain/core/runnables';
4
12
  import type { BaseMessage, AIMessage } from '@langchain/core/messages';
5
13
  import type { StructuredToolInterface } from '@langchain/core/tools';
6
14
  import type * as t from '@/types';
7
- import{ RunnableCallable } from '@/utils';
15
+ import { RunnableCallable } from '@/utils';
8
16
  import { GraphNodeKeys } from '@/common';
9
17
 
10
18
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -15,6 +23,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
15
23
  handleToolErrors = true;
16
24
  toolCallStepIds?: Map<string, string>;
17
25
  errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
26
+ private toolUsageCount: Map<string, number>;
18
27
 
19
28
  constructor({
20
29
  tools,
@@ -28,11 +37,20 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
28
37
  }: t.ToolNodeConstructorParams) {
29
38
  super({ name, tags, func: (input, config) => this.run(input, config) });
30
39
  this.tools = tools;
31
- this.toolMap = toolMap ?? new Map(tools.map(tool => [tool.name, tool]));
40
+ this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
32
41
  this.toolCallStepIds = toolCallStepIds;
33
42
  this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
34
43
  this.loadRuntimeTools = loadRuntimeTools;
35
44
  this.errorHandler = errorHandler;
45
+ this.toolUsageCount = new Map<string, number>();
46
+ }
47
+
48
+ /**
49
+ * Returns a snapshot of the current tool usage counts.
50
+ * @returns A ReadonlyMap where keys are tool names and values are their usage counts.
51
+ */
52
+ public getToolUsageCounts(): ReadonlyMap<string, number> {
53
+ return new Map(this.toolUsageCount); // Return a copy
36
54
  }
37
55
 
38
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -50,7 +68,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
50
68
  (message as AIMessage).tool_calls ?? []
51
69
  );
52
70
  this.tools = tools;
53
- this.toolMap = toolMap ?? new Map(tools.map(tool => [tool.name, tool]));
71
+ this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
54
72
  }
55
73
  const outputs = await Promise.all(
56
74
  (message as AIMessage).tool_calls?.map(async (call) => {
@@ -59,11 +77,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
59
77
  if (tool === undefined) {
60
78
  throw new Error(`Tool "${call.name}" not found.`);
61
79
  }
80
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
81
+ this.toolUsageCount.set(call.name, turn + 1);
62
82
  const args = call.args;
63
83
  const stepId = this.toolCallStepIds?.get(call.id!);
64
84
  const output = await tool.invoke(
65
- { ...call, args, type: 'tool_call', stepId },
66
- config,
85
+ { ...call, args, type: 'tool_call', stepId, turn },
86
+ config
67
87
  );
68
88
  if (
69
89
  (isBaseMessage(output) && output._getType() === 'tool') ||
@@ -86,12 +106,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
86
106
  if (isGraphInterrupt(e)) {
87
107
  throw e;
88
108
  }
89
- this.errorHandler?.({
90
- error: e,
91
- id: call.id!,
92
- name: call.name,
93
- input: call.args,
94
- }, config.metadata);
109
+ this.errorHandler?.(
110
+ {
111
+ error: e,
112
+ id: call.id!,
113
+ name: call.name,
114
+ input: call.args,
115
+ },
116
+ config.metadata
117
+ );
95
118
  return new ToolMessage({
96
119
  content: `Error: ${e.message}\n Please fix your mistakes.`,
97
120
  name: call.name,
@@ -130,4 +153,4 @@ export function toolsCondition(
130
153
  } else {
131
154
  return END;
132
155
  }
133
- }
156
+ }
@@ -0,0 +1,167 @@
1
+ /* eslint-disable no-console */
2
+ // src/tools/handlers.ts
3
+ import { nanoid } from 'nanoid';
4
+ import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
5
+ import type { Graph } from '@/graphs';
6
+ import type * as t from '@/types';
7
+ import { StepTypes, ContentTypes, ToolCallTypes } from '@/common';
8
+ import { getMessageId } from '@/messages';
9
+
10
+ export function handleToolCallChunks({
11
+ graph,
12
+ stepKey,
13
+ toolCallChunks,
14
+ }: {
15
+ graph: Graph;
16
+ stepKey: string;
17
+ toolCallChunks: ToolCallChunk[];
18
+ }): void {
19
+ let prevStepId: string;
20
+ let prevRunStep: t.RunStep | undefined;
21
+ try {
22
+ prevStepId = graph.getStepIdByKey(stepKey, graph.contentData.length - 1);
23
+ prevRunStep = graph.getRunStep(prevStepId);
24
+ } catch {
25
+ /** Edge Case: If no previous step exists, create a new message creation step */
26
+ const message_id = getMessageId(stepKey, graph, true) ?? '';
27
+ prevStepId = graph.dispatchRunStep(stepKey, {
28
+ type: StepTypes.MESSAGE_CREATION,
29
+ message_creation: {
30
+ message_id,
31
+ },
32
+ });
33
+ prevRunStep = graph.getRunStep(prevStepId);
34
+ }
35
+
36
+ const _stepId = graph.getStepIdByKey(stepKey, prevRunStep?.index);
37
+
38
+ /** Edge Case: Tool Call Run Step or `tool_call_ids` never dispatched */
39
+ const tool_calls: ToolCall[] | undefined =
40
+ prevStepId && prevRunStep && prevRunStep.type === StepTypes.MESSAGE_CREATION
41
+ ? []
42
+ : undefined;
43
+
44
+ /** Edge Case: `id` and `name` fields cannot be empty strings */
45
+ for (const toolCallChunk of toolCallChunks) {
46
+ if (toolCallChunk.name === '') {
47
+ toolCallChunk.name = undefined;
48
+ }
49
+ if (toolCallChunk.id === '') {
50
+ toolCallChunk.id = undefined;
51
+ } else if (
52
+ tool_calls != null &&
53
+ toolCallChunk.id != null &&
54
+ toolCallChunk.name != null
55
+ ) {
56
+ tool_calls.push({
57
+ args: {},
58
+ id: toolCallChunk.id,
59
+ name: toolCallChunk.name,
60
+ type: ToolCallTypes.TOOL_CALL,
61
+ });
62
+ }
63
+ }
64
+
65
+ let stepId: string = _stepId;
66
+ const alreadyDispatched =
67
+ prevRunStep?.type === StepTypes.MESSAGE_CREATION &&
68
+ graph.messageStepHasToolCalls.has(prevStepId);
69
+ if (!alreadyDispatched && tool_calls?.length === toolCallChunks.length) {
70
+ graph.dispatchMessageDelta(prevStepId, {
71
+ content: [
72
+ {
73
+ type: ContentTypes.TEXT,
74
+ text: '',
75
+ tool_call_ids: tool_calls.map((tc) => tc.id ?? ''),
76
+ },
77
+ ],
78
+ });
79
+ graph.messageStepHasToolCalls.set(prevStepId, true);
80
+ stepId = graph.dispatchRunStep(stepKey, {
81
+ type: StepTypes.TOOL_CALLS,
82
+ tool_calls,
83
+ });
84
+ }
85
+ graph.dispatchRunStepDelta(stepId, {
86
+ type: StepTypes.TOOL_CALLS,
87
+ tool_calls: toolCallChunks,
88
+ });
89
+ }
90
+
91
+ export const handleToolCalls = (
92
+ toolCalls?: ToolCall[],
93
+ metadata?: Record<string, unknown>,
94
+ graph?: Graph
95
+ ): void => {
96
+ if (!graph || !metadata) {
97
+ console.warn(`Graph or metadata not found in ${event} event`);
98
+ return;
99
+ }
100
+
101
+ if (!toolCalls) {
102
+ return;
103
+ }
104
+
105
+ if (toolCalls.length === 0) {
106
+ return;
107
+ }
108
+
109
+ const stepKey = graph.getStepKey(metadata);
110
+
111
+ for (const tool_call of toolCalls) {
112
+ const toolCallId = tool_call.id ?? `toolu_${nanoid()}`;
113
+ tool_call.id = toolCallId;
114
+ if (!toolCallId || graph.toolCallStepIds.has(toolCallId)) {
115
+ continue;
116
+ }
117
+
118
+ let prevStepId = '';
119
+ let prevRunStep: t.RunStep | undefined;
120
+ try {
121
+ prevStepId = graph.getStepIdByKey(stepKey, graph.contentData.length - 1);
122
+ prevRunStep = graph.getRunStep(prevStepId);
123
+ } catch {
124
+ // no previous step
125
+ }
126
+
127
+ const dispatchToolCallIds = (lastMessageStepId: string): void => {
128
+ graph.dispatchMessageDelta(lastMessageStepId, {
129
+ content: [
130
+ {
131
+ type: 'text',
132
+ text: '',
133
+ tool_call_ids: [toolCallId],
134
+ },
135
+ ],
136
+ });
137
+ };
138
+ /* If the previous step exists and is a message creation */
139
+ if (
140
+ prevStepId &&
141
+ prevRunStep &&
142
+ prevRunStep.type === StepTypes.MESSAGE_CREATION
143
+ ) {
144
+ dispatchToolCallIds(prevStepId);
145
+ graph.messageStepHasToolCalls.set(prevStepId, true);
146
+ /* If the previous step doesn't exist or is not a message creation */
147
+ } else if (
148
+ !prevRunStep ||
149
+ prevRunStep.type !== StepTypes.MESSAGE_CREATION
150
+ ) {
151
+ const messageId = getMessageId(stepKey, graph, true) ?? '';
152
+ const stepId = graph.dispatchRunStep(stepKey, {
153
+ type: StepTypes.MESSAGE_CREATION,
154
+ message_creation: {
155
+ message_id: messageId,
156
+ },
157
+ });
158
+ dispatchToolCallIds(stepId);
159
+ graph.messageStepHasToolCalls.set(prevStepId, true);
160
+ }
161
+
162
+ graph.dispatchRunStep(stepKey, {
163
+ type: StepTypes.TOOL_CALLS,
164
+ tool_calls: [tool_call],
165
+ });
166
+ }
167
+ };