@librechat/agents 2.4.21 → 2.4.30

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 (42) hide show
  1. package/dist/cjs/llm/anthropic/index.cjs +1 -1
  2. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/types.cjs +50 -0
  4. package/dist/cjs/llm/anthropic/types.cjs.map +1 -0
  5. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +227 -21
  6. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  7. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +1 -0
  8. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  9. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  10. package/dist/cjs/messages/core.cjs +91 -51
  11. package/dist/cjs/messages/core.cjs.map +1 -1
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/esm/llm/anthropic/index.mjs +1 -1
  14. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  15. package/dist/esm/llm/anthropic/types.mjs +48 -0
  16. package/dist/esm/llm/anthropic/types.mjs.map +1 -0
  17. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +228 -22
  18. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  19. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +1 -0
  20. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  21. package/dist/esm/llm/openai/index.mjs.map +1 -1
  22. package/dist/esm/messages/core.mjs +91 -51
  23. package/dist/esm/messages/core.mjs.map +1 -1
  24. package/dist/esm/run.mjs.map +1 -1
  25. package/dist/types/llm/anthropic/index.d.ts +3 -4
  26. package/dist/types/llm/anthropic/types.d.ts +4 -35
  27. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +2 -2
  28. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -3
  29. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +22 -0
  30. package/dist/types/llm/openai/index.d.ts +3 -2
  31. package/dist/types/messages/core.d.ts +1 -1
  32. package/dist/types/tools/example.d.ts +21 -3
  33. package/package.json +9 -9
  34. package/src/llm/anthropic/index.ts +6 -5
  35. package/src/llm/anthropic/llm.spec.ts +176 -179
  36. package/src/llm/anthropic/types.ts +64 -39
  37. package/src/llm/anthropic/utils/message_inputs.ts +275 -37
  38. package/src/llm/anthropic/utils/message_outputs.ts +4 -21
  39. package/src/llm/anthropic/utils/output_parsers.ts +114 -0
  40. package/src/llm/openai/index.ts +7 -6
  41. package/src/messages/core.ts +197 -96
  42. package/src/run.ts +1 -1
@@ -0,0 +1,114 @@
1
+ /* eslint-disable @typescript-eslint/explicit-function-return-type */
2
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
3
+ import { z } from 'zod';
4
+ import {
5
+ BaseLLMOutputParser,
6
+ OutputParserException,
7
+ } from '@langchain/core/output_parsers';
8
+ import { JsonOutputKeyToolsParserParams } from '@langchain/core/output_parsers/openai_tools';
9
+ import { ChatGeneration } from '@langchain/core/outputs';
10
+ import { ToolCall } from '@langchain/core/messages/tool';
11
+
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ interface AnthropicToolsOutputParserParams<T extends Record<string, any>>
14
+ extends JsonOutputKeyToolsParserParams<T> {}
15
+
16
+ export class AnthropicToolsOutputParser<
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ T extends Record<string, any> = Record<string, any>,
19
+ > extends BaseLLMOutputParser<T> {
20
+ static lc_name() {
21
+ return 'AnthropicToolsOutputParser';
22
+ }
23
+
24
+ lc_namespace = ['langchain', 'anthropic', 'output_parsers'];
25
+
26
+ returnId = false;
27
+
28
+ /** The type of tool calls to return. */
29
+ keyName: string;
30
+
31
+ /** Whether to return only the first tool call. */
32
+ returnSingle = false;
33
+
34
+ zodSchema?: z.ZodType<T>;
35
+
36
+ constructor(params: AnthropicToolsOutputParserParams<T>) {
37
+ super(params);
38
+ this.keyName = params.keyName;
39
+ this.returnSingle = params.returnSingle ?? this.returnSingle;
40
+ this.zodSchema = params.zodSchema;
41
+ }
42
+
43
+ protected async _validateResult(result: unknown): Promise<T> {
44
+ let parsedResult = result;
45
+ if (typeof result === 'string') {
46
+ try {
47
+ parsedResult = JSON.parse(result);
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ } catch (e: any) {
50
+ throw new OutputParserException(
51
+ `Failed to parse. Text: "${JSON.stringify(
52
+ result,
53
+ null,
54
+ 2
55
+ )}". Error: ${JSON.stringify(e.message)}`,
56
+ result
57
+ );
58
+ }
59
+ } else {
60
+ parsedResult = result;
61
+ }
62
+ if (this.zodSchema === undefined) {
63
+ return parsedResult as T;
64
+ }
65
+ const zodParsedResult = await this.zodSchema.safeParseAsync(parsedResult);
66
+ if (zodParsedResult.success) {
67
+ return zodParsedResult.data;
68
+ } else {
69
+ throw new OutputParserException(
70
+ `Failed to parse. Text: "${JSON.stringify(
71
+ result,
72
+ null,
73
+ 2
74
+ )}". Error: ${JSON.stringify(zodParsedResult.error.errors)}`,
75
+ JSON.stringify(parsedResult, null, 2)
76
+ );
77
+ }
78
+ }
79
+
80
+ async parseResult(generations: ChatGeneration[]): Promise<T> {
81
+ const tools = generations.flatMap((generation) => {
82
+ const { message } = generation;
83
+ if (!Array.isArray(message.content)) {
84
+ return [];
85
+ }
86
+ const tool = extractToolCalls(message.content)[0];
87
+ return tool;
88
+ });
89
+ if (tools[0] === undefined) {
90
+ throw new Error(
91
+ 'No parseable tool calls provided to AnthropicToolsOutputParser.'
92
+ );
93
+ }
94
+ const [tool] = tools;
95
+ const validatedResult = await this._validateResult(tool.args);
96
+ return validatedResult;
97
+ }
98
+ }
99
+
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ export function extractToolCalls(content: Record<string, any>[]) {
102
+ const toolCalls: ToolCall[] = [];
103
+ for (const block of content) {
104
+ if (block.type === 'tool_use') {
105
+ toolCalls.push({
106
+ name: block.name,
107
+ args: block.input,
108
+ id: block.id,
109
+ type: 'tool_call',
110
+ });
111
+ }
112
+ }
113
+ return toolCalls;
114
+ }
@@ -7,6 +7,7 @@ import {
7
7
  ChatOpenAI as OriginalChatOpenAI,
8
8
  AzureChatOpenAI as OriginalAzureChatOpenAI,
9
9
  } from '@langchain/openai';
10
+ import type { OpenAICoreRequestOptions } from 'node_modules/@langchain/deepseek/node_modules/@langchain/openai';
10
11
  import type * as t from '@langchain/openai';
11
12
 
12
13
  function createAbortHandler(controller: AbortController): () => void {
@@ -191,8 +192,8 @@ export class ChatDeepSeek extends OriginalChatDeepSeek {
191
192
  return this.client;
192
193
  }
193
194
  protected _getClientOptions(
194
- options?: t.OpenAICoreRequestOptions
195
- ): t.OpenAICoreRequestOptions {
195
+ options?: OpenAICoreRequestOptions
196
+ ): OpenAICoreRequestOptions {
196
197
  if (!(this.client as OpenAIClient | undefined)) {
197
198
  const openAIEndpointConfig: t.OpenAIEndpointConfig = {
198
199
  baseURL: this.clientConfig.baseURL,
@@ -214,7 +215,7 @@ export class ChatDeepSeek extends OriginalChatDeepSeek {
214
215
  const requestOptions = {
215
216
  ...this.clientConfig,
216
217
  ...options,
217
- } as t.OpenAICoreRequestOptions;
218
+ } as OpenAICoreRequestOptions;
218
219
  return requestOptions;
219
220
  }
220
221
  }
@@ -224,8 +225,8 @@ export class ChatXAI extends OriginalChatXAI {
224
225
  return this.client;
225
226
  }
226
227
  protected _getClientOptions(
227
- options?: t.OpenAICoreRequestOptions
228
- ): t.OpenAICoreRequestOptions {
228
+ options?: OpenAICoreRequestOptions
229
+ ): OpenAICoreRequestOptions {
229
230
  if (!(this.client as OpenAIClient | undefined)) {
230
231
  const openAIEndpointConfig: t.OpenAIEndpointConfig = {
231
232
  baseURL: this.clientConfig.baseURL,
@@ -247,7 +248,7 @@ export class ChatXAI extends OriginalChatXAI {
247
248
  const requestOptions = {
248
249
  ...this.clientConfig,
249
250
  ...options,
250
- } as t.OpenAICoreRequestOptions;
251
+ } as OpenAICoreRequestOptions;
251
252
  return requestOptions;
252
253
  }
253
254
  }
@@ -1,5 +1,11 @@
1
1
  // src/messages.ts
2
- import { AIMessageChunk, HumanMessage, ToolMessage, AIMessage, BaseMessage } from '@langchain/core/messages';
2
+ import {
3
+ AIMessageChunk,
4
+ HumanMessage,
5
+ ToolMessage,
6
+ AIMessage,
7
+ BaseMessage,
8
+ } from '@langchain/core/messages';
3
9
  import type { ToolCall } from '@langchain/core/messages/tool';
4
10
  import type * as t from '@/types';
5
11
  import { Providers } from '@/common';
@@ -7,7 +13,7 @@ import { Providers } from '@/common';
7
13
  export function getConverseOverrideMessage({
8
14
  userMessage,
9
15
  lastMessageX,
10
- lastMessageY
16
+ lastMessageY,
11
17
  }: {
12
18
  userMessage: string[];
13
19
  lastMessageX: AIMessageChunk | null;
@@ -43,13 +49,22 @@ const allowedTypesByProvider: Record<string, string[]> = {
43
49
  const modifyContent = ({
44
50
  provider,
45
51
  messageType,
46
- content
52
+ content,
47
53
  }: {
48
- provider: Providers, messageType: string, content: t.ExtendedMessageContent[]
54
+ provider: Providers;
55
+ messageType: string;
56
+ content: t.ExtendedMessageContent[];
49
57
  }): t.ExtendedMessageContent[] => {
50
- const allowedTypes = allowedTypesByProvider[provider] ?? allowedTypesByProvider.default;
51
- return content.map(item => {
52
- if (item && typeof item === 'object' && 'type' in item && item.type != null && item.type) {
58
+ const allowedTypes =
59
+ allowedTypesByProvider[provider] ?? allowedTypesByProvider.default;
60
+ return content.map((item) => {
61
+ if (
62
+ item &&
63
+ typeof item === 'object' &&
64
+ 'type' in item &&
65
+ item.type != null &&
66
+ item.type
67
+ ) {
53
68
  let newType = item.type;
54
69
  if (newType.endsWith('_delta')) {
55
70
  newType = newType.replace('_delta', '');
@@ -59,7 +74,12 @@ const modifyContent = ({
59
74
  }
60
75
 
61
76
  /* Handle the edge case for empty object 'tool_use' input in AI messages */
62
- if (messageType === 'ai' && newType === 'tool_use' && 'input' in item && item.input === '') {
77
+ if (
78
+ messageType === 'ai' &&
79
+ newType === 'tool_use' &&
80
+ 'input' in item &&
81
+ item.input === ''
82
+ ) {
63
83
  return { ...item, type: newType, input: '{}' };
64
84
  }
65
85
 
@@ -69,7 +89,9 @@ const modifyContent = ({
69
89
  });
70
90
  };
71
91
 
72
- type ContentBlock = Partial<t.BedrockReasoningContentText> | t.MessageDeltaUpdate;
92
+ type ContentBlock =
93
+ | Partial<t.BedrockReasoningContentText>
94
+ | t.MessageDeltaUpdate;
73
95
 
74
96
  function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
75
97
  const reduced: ContentBlock[] = [];
@@ -78,14 +100,25 @@ function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
78
100
  const lastBlock = reduced[reduced.length - 1] as ContentBlock | undefined;
79
101
 
80
102
  // Merge consecutive 'reasoning_content'
81
- if (block.type === 'reasoning_content' && lastBlock?.type === 'reasoning_content') {
103
+ if (
104
+ block.type === 'reasoning_content' &&
105
+ lastBlock?.type === 'reasoning_content'
106
+ ) {
82
107
  // append text if exists
83
108
  if (block.reasoningText?.text != null && block.reasoningText.text) {
84
- (lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']).text = (lastBlock.reasoningText?.text ?? '') + block.reasoningText.text;
109
+ (
110
+ lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']
111
+ ).text =
112
+ (lastBlock.reasoningText?.text ?? '') + block.reasoningText.text;
85
113
  }
86
114
  // preserve the signature if exists
87
- if (block.reasoningText?.signature != null && block.reasoningText.signature) {
88
- (lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']).signature = block.reasoningText.signature;
115
+ if (
116
+ block.reasoningText?.signature != null &&
117
+ block.reasoningText.signature
118
+ ) {
119
+ (
120
+ lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']
121
+ ).signature = block.reasoningText.signature;
89
122
  }
90
123
  }
91
124
  // Merge consecutive 'text'
@@ -102,19 +135,35 @@ function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
102
135
  return reduced;
103
136
  }
104
137
 
105
- export function modifyDeltaProperties(provider: Providers, obj?: AIMessageChunk): AIMessageChunk | undefined {
138
+ export function modifyDeltaProperties(
139
+ provider: Providers,
140
+ obj?: AIMessageChunk
141
+ ): AIMessageChunk | undefined {
106
142
  if (!obj || typeof obj !== 'object') return obj;
107
143
 
108
- const messageType = obj._getType ? obj._getType() : '';
144
+ const messageType = (obj as Partial<AIMessageChunk>)._getType
145
+ ? obj._getType()
146
+ : '';
109
147
 
110
148
  if (provider === Providers.BEDROCK && Array.isArray(obj.content)) {
111
149
  obj.content = reduceBlocks(obj.content as ContentBlock[]);
112
150
  }
113
151
  if (Array.isArray(obj.content)) {
114
- obj.content = modifyContent({ provider, messageType, content: obj.content });
152
+ obj.content = modifyContent({
153
+ provider,
154
+ messageType,
155
+ content: obj.content,
156
+ });
115
157
  }
116
- if (obj.lc_kwargs && Array.isArray(obj.lc_kwargs.content)) {
117
- obj.lc_kwargs.content = modifyContent({ provider, messageType, content: obj.lc_kwargs.content });
158
+ if (
159
+ (obj as Partial<AIMessageChunk>).lc_kwargs &&
160
+ Array.isArray(obj.lc_kwargs.content)
161
+ ) {
162
+ obj.lc_kwargs.content = modifyContent({
163
+ provider,
164
+ messageType,
165
+ content: obj.lc_kwargs.content,
166
+ });
118
167
  }
119
168
  return obj;
120
169
  }
@@ -124,48 +173,65 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
124
173
  return new AIMessage({ content: message.content });
125
174
  }
126
175
 
127
- const toolCallMap = new Map(message.tool_calls.map(tc => [tc.id, tc]));
176
+ const toolCallMap = new Map(message.tool_calls.map((tc) => [tc.id, tc]));
128
177
  let formattedContent: string | t.ExtendedMessageContent[];
129
178
 
130
179
  if (Array.isArray(message.content)) {
131
- formattedContent = message.content.reduce<t.ExtendedMessageContent[]>((acc, item) => {
132
- if (typeof item === 'object' && item !== null) {
133
- const extendedItem = item as t.ExtendedMessageContent;
134
- if (extendedItem.type === 'text' && extendedItem.text != null && extendedItem.text) {
135
- acc.push({ type: 'text', text: extendedItem.text });
136
- } else if (extendedItem.type === 'tool_use' && extendedItem.id != null && extendedItem.id) {
137
- const toolCall = toolCallMap.get(extendedItem.id);
138
- if (toolCall) {
139
- acc.push({
140
- type: 'tool_use',
141
- id: extendedItem.id,
142
- name: toolCall.name,
143
- input: toolCall.args as unknown as string
144
- });
145
- }
146
- } else if ('input' in extendedItem && extendedItem.input != null && extendedItem.input) {
147
- try {
148
- const parsedInput = JSON.parse(extendedItem.input);
149
- const toolCall = message.tool_calls?.find(tc => tc.args.input === parsedInput.input);
180
+ formattedContent = message.content.reduce<t.ExtendedMessageContent[]>(
181
+ (acc, item) => {
182
+ if (typeof item === 'object') {
183
+ const extendedItem = item as t.ExtendedMessageContent;
184
+ if (
185
+ extendedItem.type === 'text' &&
186
+ extendedItem.text != null &&
187
+ extendedItem.text
188
+ ) {
189
+ acc.push({ type: 'text', text: extendedItem.text });
190
+ } else if (
191
+ extendedItem.type === 'tool_use' &&
192
+ extendedItem.id != null &&
193
+ extendedItem.id
194
+ ) {
195
+ const toolCall = toolCallMap.get(extendedItem.id);
150
196
  if (toolCall) {
151
197
  acc.push({
152
198
  type: 'tool_use',
153
- id: toolCall.id,
199
+ id: extendedItem.id,
154
200
  name: toolCall.name,
155
- input: toolCall.args as unknown as string
201
+ input: toolCall.args as unknown as string,
156
202
  });
157
203
  }
158
- } catch {
159
- if (extendedItem.input) {
160
- acc.push({ type: 'text', text: extendedItem.input });
204
+ } else if (
205
+ 'input' in extendedItem &&
206
+ extendedItem.input != null &&
207
+ extendedItem.input
208
+ ) {
209
+ try {
210
+ const parsedInput = JSON.parse(extendedItem.input);
211
+ const toolCall = message.tool_calls?.find(
212
+ (tc) => tc.args.input === parsedInput.input
213
+ );
214
+ if (toolCall) {
215
+ acc.push({
216
+ type: 'tool_use',
217
+ id: toolCall.id,
218
+ name: toolCall.name,
219
+ input: toolCall.args as unknown as string,
220
+ });
221
+ }
222
+ } catch {
223
+ if (extendedItem.input) {
224
+ acc.push({ type: 'text', text: extendedItem.input });
225
+ }
161
226
  }
162
227
  }
228
+ } else if (typeof item === 'string') {
229
+ acc.push({ type: 'text', text: item });
163
230
  }
164
- } else if (typeof item === 'string') {
165
- acc.push({ type: 'text', text: item });
166
- }
167
- return acc;
168
- }, []);
231
+ return acc;
232
+ },
233
+ []
234
+ );
169
235
  } else if (typeof message.content === 'string') {
170
236
  formattedContent = message.content;
171
237
  } else {
@@ -179,39 +245,48 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
179
245
  // type: 'tool_call',
180
246
  // }));
181
247
 
182
- const formattedToolCalls: t.AgentToolCall[] = message.tool_calls.map(toolCall => ({
183
- id: toolCall.id ?? '',
184
- type: 'function',
185
- function: {
186
- name: toolCall.name,
187
- arguments: toolCall.args
188
- }
189
- }));
248
+ const formattedToolCalls: t.AgentToolCall[] = message.tool_calls.map(
249
+ (toolCall) => ({
250
+ id: toolCall.id ?? '',
251
+ type: 'function',
252
+ function: {
253
+ name: toolCall.name,
254
+ arguments: toolCall.args,
255
+ },
256
+ })
257
+ );
190
258
 
191
259
  return new AIMessage({
192
260
  content: formattedContent,
193
261
  tool_calls: formattedToolCalls as ToolCall[],
194
262
  additional_kwargs: {
195
263
  ...message.additional_kwargs,
196
- }
264
+ },
197
265
  });
198
266
  }
199
267
 
200
- export function convertMessagesToContent(messages: BaseMessage[]): t.MessageContentComplex[] {
268
+ export function convertMessagesToContent(
269
+ messages: BaseMessage[]
270
+ ): t.MessageContentComplex[] {
201
271
  const processedContent: t.MessageContentComplex[] = [];
202
272
 
203
273
  const addContentPart = (message: BaseMessage | null): void => {
204
- const content = message?.lc_kwargs.content != null ? message.lc_kwargs.content : message?.content;
274
+ const content =
275
+ message?.lc_kwargs.content != null
276
+ ? message.lc_kwargs.content
277
+ : message?.content;
205
278
  if (content === undefined) {
206
279
  return;
207
280
  }
208
281
  if (typeof content === 'string') {
209
282
  processedContent.push({
210
283
  type: 'text',
211
- text: content
284
+ text: content,
212
285
  });
213
286
  } else if (Array.isArray(content)) {
214
- const filteredContent = content.filter(item => item && item.type !== 'tool_use');
287
+ const filteredContent = content.filter(
288
+ (item) => item != null && item.type !== 'tool_use'
289
+ );
215
290
  processedContent.push(...filteredContent);
216
291
  }
217
292
  };
@@ -223,10 +298,13 @@ export function convertMessagesToContent(messages: BaseMessage[]): t.MessageCont
223
298
  const message = messages[i] as BaseMessage | null;
224
299
  const messageType = message?._getType();
225
300
 
226
- if (messageType === 'ai' && (message as AIMessage).tool_calls?.length) {
301
+ if (
302
+ messageType === 'ai' &&
303
+ ((message as AIMessage).tool_calls?.length ?? 0) > 0
304
+ ) {
227
305
  const tool_calls = (message as AIMessage).tool_calls || [];
228
306
  for (const tool_call of tool_calls) {
229
- if (!tool_call.id) {
307
+ if (tool_call.id == null || !tool_call.id) {
230
308
  continue;
231
309
  }
232
310
 
@@ -236,7 +314,10 @@ export function convertMessagesToContent(messages: BaseMessage[]): t.MessageCont
236
314
  addContentPart(message);
237
315
  currentAIMessageIndex = processedContent.length - 1;
238
316
  continue;
239
- } else if (messageType === 'tool' && (message as ToolMessage).tool_call_id) {
317
+ } else if (
318
+ messageType === 'tool' &&
319
+ (message as ToolMessage).tool_call_id
320
+ ) {
240
321
  const id = (message as ToolMessage).tool_call_id;
241
322
  const output = (message as ToolMessage).content;
242
323
  const tool_call = toolCallMap.get(id);
@@ -265,35 +346,41 @@ export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
265
346
  if (!(lastMessage instanceof ToolMessage)) return;
266
347
 
267
348
  // Find the latest AIMessage with tool_calls that this tool message belongs to
268
- const latestAIParentIndex = findLastIndex(messages,
269
- msg => (msg instanceof AIMessageChunk &&
270
- (msg.tool_calls?.length ?? 0) > 0 &&
271
- msg.tool_calls?.some(tc => tc.id === lastMessage.tool_call_id)) ?? false
349
+ const latestAIParentIndex = findLastIndex(
350
+ messages,
351
+ (msg) =>
352
+ (msg instanceof AIMessageChunk &&
353
+ (msg.tool_calls?.length ?? 0) > 0 &&
354
+ msg.tool_calls?.some((tc) => tc.id === lastMessage.tool_call_id)) ??
355
+ false
272
356
  );
273
357
 
274
358
  if (latestAIParentIndex === -1) return;
275
359
 
276
360
  // Check if any tool message after the AI message has array artifact content
277
361
  const hasArtifactContent = messages.some(
278
- (msg, i) => i > latestAIParentIndex
279
- && msg instanceof ToolMessage
280
- && msg.artifact != null
281
- && msg.artifact?.content != null
282
- && Array.isArray(msg.artifact.content)
362
+ (msg, i) =>
363
+ i > latestAIParentIndex &&
364
+ msg instanceof ToolMessage &&
365
+ msg.artifact != null &&
366
+ msg.artifact?.content != null &&
367
+ Array.isArray(msg.artifact.content)
283
368
  );
284
369
 
285
370
  if (!hasArtifactContent) return;
286
371
 
287
372
  const message = messages[latestAIParentIndex] as AIMessageChunk;
288
- const toolCallIds = message.tool_calls?.map(tc => tc.id) ?? [];
373
+ const toolCallIds = message.tool_calls?.map((tc) => tc.id) ?? [];
289
374
 
290
375
  for (let j = latestAIParentIndex + 1; j < messages.length; j++) {
291
376
  const msg = messages[j];
292
- if (msg instanceof ToolMessage &&
293
- toolCallIds.includes(msg.tool_call_id) &&
294
- msg.artifact != null &&
295
- Array.isArray(msg.artifact?.content) &&
296
- Array.isArray(msg.content)) {
377
+ if (
378
+ msg instanceof ToolMessage &&
379
+ toolCallIds.includes(msg.tool_call_id) &&
380
+ msg.artifact != null &&
381
+ Array.isArray(msg.artifact?.content) &&
382
+ Array.isArray(msg.content)
383
+ ) {
297
384
  msg.content = msg.content.concat(msg.artifact.content);
298
385
  }
299
386
  }
@@ -304,21 +391,25 @@ export function formatArtifactPayload(messages: BaseMessage[]): void {
304
391
  if (!(lastMessageY instanceof ToolMessage)) return;
305
392
 
306
393
  // Find the latest AIMessage with tool_calls that this tool message belongs to
307
- const latestAIParentIndex = findLastIndex(messages,
308
- msg => (msg instanceof AIMessageChunk &&
309
- (msg.tool_calls?.length ?? 0) > 0 &&
310
- msg.tool_calls?.some(tc => tc.id === lastMessageY.tool_call_id)) ?? false
394
+ const latestAIParentIndex = findLastIndex(
395
+ messages,
396
+ (msg) =>
397
+ (msg instanceof AIMessageChunk &&
398
+ (msg.tool_calls?.length ?? 0) > 0 &&
399
+ msg.tool_calls?.some((tc) => tc.id === lastMessageY.tool_call_id)) ??
400
+ false
311
401
  );
312
402
 
313
403
  if (latestAIParentIndex === -1) return;
314
404
 
315
405
  // Check if any tool message after the AI message has array artifact content
316
406
  const hasArtifactContent = messages.some(
317
- (msg, i) => i > latestAIParentIndex
318
- && msg instanceof ToolMessage
319
- && msg.artifact != null
320
- && msg.artifact?.content != null
321
- && Array.isArray(msg.artifact.content)
407
+ (msg, i) =>
408
+ i > latestAIParentIndex &&
409
+ msg instanceof ToolMessage &&
410
+ msg.artifact != null &&
411
+ msg.artifact?.content != null &&
412
+ Array.isArray(msg.artifact.content)
322
413
  );
323
414
 
324
415
  if (!hasArtifactContent) return;
@@ -326,20 +417,27 @@ export function formatArtifactPayload(messages: BaseMessage[]): void {
326
417
  // Collect all relevant tool messages and their artifacts
327
418
  const relevantMessages = messages
328
419
  .slice(latestAIParentIndex + 1)
329
- .filter(msg => msg instanceof ToolMessage) as ToolMessage[];
420
+ .filter((msg) => msg instanceof ToolMessage) as ToolMessage[];
330
421
 
331
422
  // Aggregate all content and artifacts
332
423
  const aggregatedContent: t.MessageContentComplex[] = [];
333
424
 
334
- relevantMessages.forEach(msg => {
425
+ relevantMessages.forEach((msg) => {
335
426
  if (!Array.isArray(msg.artifact?.content)) {
336
427
  return;
337
428
  }
338
- if (!Array.isArray(msg.content)) {
339
- return;
429
+ let currentContent = msg.content;
430
+ if (!Array.isArray(currentContent)) {
431
+ currentContent = [
432
+ {
433
+ type: 'text',
434
+ text: msg.content,
435
+ },
436
+ ];
340
437
  }
341
- aggregatedContent.push(...msg.content);
342
- msg.content = 'Tool response is included in the next message as a Human message';
438
+ aggregatedContent.push(...currentContent);
439
+ msg.content =
440
+ 'Tool response is included in the next message as a Human message';
343
441
  aggregatedContent.push(...msg.artifact.content);
344
442
  });
345
443
 
@@ -349,11 +447,14 @@ export function formatArtifactPayload(messages: BaseMessage[]): void {
349
447
  }
350
448
  }
351
449
 
352
- export function findLastIndex<T>(array: T[], predicate: (value: T) => boolean): number {
450
+ export function findLastIndex<T>(
451
+ array: T[],
452
+ predicate: (value: T) => boolean
453
+ ): number {
353
454
  for (let i = array.length - 1; i >= 0; i--) {
354
455
  if (predicate(array[i])) {
355
456
  return i;
356
457
  }
357
458
  }
358
459
  return -1;
359
- }
460
+ }
package/src/run.ts CHANGED
@@ -141,7 +141,7 @@ export class Run<T extends t.BaseGraphState> {
141
141
  }
142
142
 
143
143
  const jsonSchema = zodToJsonSchema(
144
- tool.schema.describe(tool.description ?? ''),
144
+ (tool.schema as t.ZodObjectAny).describe(tool.description ?? ''),
145
145
  tool.name
146
146
  );
147
147
  return (