@librechat/agents 1.8.7 → 1.8.9

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 (74) hide show
  1. package/dist/cjs/common/enum.cjs +1 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/events.cjs +8 -1
  4. package/dist/cjs/events.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/llm.cjs +117 -0
  6. package/dist/cjs/llm/anthropic/llm.cjs.map +1 -0
  7. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +277 -0
  8. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -0
  9. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +135 -0
  10. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -0
  11. package/dist/cjs/llm/providers.cjs +5 -4
  12. package/dist/cjs/llm/providers.cjs.map +1 -1
  13. package/dist/cjs/llm/text.cjs +58 -0
  14. package/dist/cjs/llm/text.cjs.map +1 -0
  15. package/dist/cjs/main.cjs +3 -0
  16. package/dist/cjs/main.cjs.map +1 -1
  17. package/dist/cjs/messages.cjs +14 -54
  18. package/dist/cjs/messages.cjs.map +1 -1
  19. package/dist/cjs/stream.cjs +63 -48
  20. package/dist/cjs/stream.cjs.map +1 -1
  21. package/dist/cjs/tools/ToolNode.cjs +20 -5
  22. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  23. package/dist/cjs/utils/misc.cjs +49 -0
  24. package/dist/cjs/utils/misc.cjs.map +1 -0
  25. package/dist/esm/common/enum.mjs +1 -0
  26. package/dist/esm/common/enum.mjs.map +1 -1
  27. package/dist/esm/events.mjs +8 -1
  28. package/dist/esm/events.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/llm.mjs +115 -0
  30. package/dist/esm/llm/anthropic/llm.mjs.map +1 -0
  31. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +274 -0
  32. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -0
  33. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +133 -0
  34. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -0
  35. package/dist/esm/llm/providers.mjs +5 -4
  36. package/dist/esm/llm/providers.mjs.map +1 -1
  37. package/dist/esm/llm/text.mjs +56 -0
  38. package/dist/esm/llm/text.mjs.map +1 -0
  39. package/dist/esm/main.mjs +2 -1
  40. package/dist/esm/main.mjs.map +1 -1
  41. package/dist/esm/messages.mjs +14 -54
  42. package/dist/esm/messages.mjs.map +1 -1
  43. package/dist/esm/stream.mjs +63 -49
  44. package/dist/esm/stream.mjs.map +1 -1
  45. package/dist/esm/tools/ToolNode.mjs +22 -7
  46. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  47. package/dist/esm/utils/misc.mjs +47 -0
  48. package/dist/esm/utils/misc.mjs.map +1 -0
  49. package/dist/types/common/enum.d.ts +2 -1
  50. package/dist/types/llm/anthropic/types.d.ts +4 -0
  51. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
  52. package/dist/types/llm/text.d.ts +6 -6
  53. package/dist/types/stream.d.ts +2 -0
  54. package/dist/types/types/llm.d.ts +6 -1
  55. package/dist/types/utils/index.d.ts +1 -0
  56. package/dist/types/utils/misc.d.ts +6 -0
  57. package/package.json +8 -7
  58. package/src/common/enum.ts +1 -0
  59. package/src/events.ts +9 -1
  60. package/src/llm/anthropic/llm.ts +1 -1
  61. package/src/llm/anthropic/types.ts +7 -1
  62. package/src/llm/anthropic/utils/message_inputs.ts +86 -8
  63. package/src/llm/providers.ts +6 -4
  64. package/src/llm/text.ts +30 -45
  65. package/src/messages.ts +14 -65
  66. package/src/scripts/args.ts +1 -1
  67. package/src/scripts/code_exec.ts +4 -0
  68. package/src/scripts/simple.ts +4 -0
  69. package/src/stream.ts +68 -50
  70. package/src/tools/ToolNode.ts +25 -9
  71. package/src/types/llm.ts +6 -1
  72. package/src/utils/index.ts +1 -0
  73. package/src/utils/llmConfig.ts +6 -0
  74. package/src/utils/misc.ts +45 -0
@@ -18,3 +18,7 @@ export type AnthropicToolChoice = {
18
18
  name: string;
19
19
  } | 'any' | 'auto' | 'none' | string;
20
20
  export type ChatAnthropicToolType = AnthropicTool | BindToolsInput;
21
+ export type AnthropicTextBlockParam = Anthropic.Messages.TextBlockParam;
22
+ export type AnthropicImageBlockParam = Anthropic.Messages.ImageBlockParam;
23
+ export type AnthropicToolUseBlockParam = Anthropic.Messages.ToolUseBlockParam;
24
+ export type AnthropicToolResultBlockParam = Anthropic.Messages.ToolResultBlockParam;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { BaseMessage } from '@langchain/core/messages';
5
5
  import { ToolCall } from '@langchain/core/messages/tool';
6
- import type { AnthropicMessageCreateParams, AnthropicToolResponse } from '@/llm/anthropic/types';
6
+ import type { AnthropicToolResponse, AnthropicMessageCreateParams } from '@/llm/anthropic/types';
7
7
  export declare function _convertLangChainToolCallToAnthropic(toolCall: ToolCall): AnthropicToolResponse;
8
8
  /**
9
9
  * Formats messages as a prompt for the model.
@@ -1,21 +1,21 @@
1
- import { Readable } from 'stream';
2
- import type { ReadableOptions } from 'stream';
3
- export interface TextStreamOptions extends ReadableOptions {
1
+ export interface TextStreamOptions {
4
2
  minChunkSize?: number;
5
3
  maxChunkSize?: number;
6
4
  delay?: number;
5
+ firstWordChunk?: boolean;
7
6
  }
8
7
  export type ProgressCallback = (chunk: string) => void;
9
8
  export type PostChunkCallback = (chunk: string) => void;
10
- export declare class TextStream extends Readable {
9
+ export declare class TextStream {
11
10
  private text;
12
11
  private currentIndex;
13
12
  private minChunkSize;
14
13
  private maxChunkSize;
15
14
  private delay;
15
+ private firstWordChunk;
16
16
  constructor(text: string, options?: TextStreamOptions);
17
- _read(): void;
18
17
  private randomInt;
19
- processTextStream(progressCallback: ProgressCallback): Promise<void>;
18
+ private static readonly BOUNDARIES;
19
+ private findFirstWordBoundary;
20
20
  generateText(progressCallback?: ProgressCallback): AsyncGenerator<string, void, unknown>;
21
21
  }
@@ -1,6 +1,8 @@
1
+ import type { ToolCall } from '@langchain/core/messages/tool';
1
2
  import type { Graph } from '@/graphs';
2
3
  import type * as t from '@/types';
3
4
  import { GraphEvents } from '@/common';
5
+ export declare const handleToolCalls: (toolCalls?: ToolCall[], metadata?: Record<string, unknown>, graph?: Graph) => void;
4
6
  export declare class ChatModelStreamHandler implements t.EventHandler {
5
7
  handle(event: string, data: t.StreamEventData, metadata?: Record<string, unknown>, graph?: Graph): void;
6
8
  }
@@ -5,12 +5,14 @@ import { ChatMistralAI } from '@langchain/mistralai';
5
5
  import { ChatBedrockConverse } from '@langchain/aws';
6
6
  import { ChatVertexAI } from '@langchain/google-vertexai';
7
7
  import { BedrockChat } from '@langchain/community/chat_models/bedrock/web';
8
+ import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
8
9
  import type { Runnable } from '@langchain/core/runnables';
9
10
  import type { StructuredTool } from '@langchain/core/tools';
10
11
  import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
11
12
  import type { BedrockChatFields } from '@langchain/community/chat_models/bedrock/web';
12
13
  import type { ChatOpenAIFields } from '@langchain/openai';
13
14
  import type { OpenAI as OpenAIClient } from 'openai';
15
+ import type { GoogleGenerativeAIChatInput } from '@langchain/google-genai';
14
16
  import type { ChatVertexAIInput } from '@langchain/google-vertexai';
15
17
  import type { ChatBedrockConverseInput } from '@langchain/aws';
16
18
  import type { ChatMistralAIInput } from '@langchain/mistralai';
@@ -26,7 +28,8 @@ export type MistralAIClientOptions = ChatMistralAIInput;
26
28
  export type VertexAIClientOptions = ChatVertexAIInput;
27
29
  export type BedrockClientOptions = BedrockChatFields;
28
30
  export type BedrockConverseClientOptions = ChatBedrockConverseInput;
29
- export type ClientOptions = OpenAIClientOptions | OllamaClientOptions | AnthropicClientOptions | MistralAIClientOptions | VertexAIClientOptions | BedrockClientOptions | BedrockConverseClientOptions;
31
+ export type GoogleClientOptions = GoogleGenerativeAIChatInput;
32
+ export type ClientOptions = OpenAIClientOptions | OllamaClientOptions | AnthropicClientOptions | MistralAIClientOptions | VertexAIClientOptions | BedrockClientOptions | BedrockConverseClientOptions | GoogleClientOptions;
30
33
  export type LLMConfig = {
31
34
  provider: Providers;
32
35
  } & ClientOptions;
@@ -38,6 +41,7 @@ export type ProviderOptionsMap = {
38
41
  [Providers.VERTEXAI]: VertexAIClientOptions;
39
42
  [Providers.BEDROCK_LEGACY]: BedrockClientOptions;
40
43
  [Providers.BEDROCK]: BedrockConverseClientOptions;
44
+ [Providers.GOOGLE]: GoogleClientOptions;
41
45
  };
42
46
  export type ChatModelMap = {
43
47
  [Providers.OPENAI]: ChatOpenAI;
@@ -47,6 +51,7 @@ export type ChatModelMap = {
47
51
  [Providers.VERTEXAI]: ChatVertexAI;
48
52
  [Providers.BEDROCK_LEGACY]: BedrockChat;
49
53
  [Providers.BEDROCK]: ChatBedrockConverse;
54
+ [Providers.GOOGLE]: ChatGoogleGenerativeAI;
50
55
  };
51
56
  export type ChatModelConstructorMap = {
52
57
  [P in Providers]: new (config: ProviderOptionsMap[P]) => ChatModelMap[P];
@@ -1,2 +1,3 @@
1
1
  export * from './graph';
2
+ export * from './misc';
2
3
  export * from './run';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Recursively unescapes all string values in an object
3
+ * @param obj The object to unescape
4
+ * @returns The unescaped object
5
+ */
6
+ export declare function unescapeObject(obj: unknown, key?: string): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "1.8.7",
3
+ "version": "1.8.9",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -42,10 +42,10 @@
42
42
  "start:cli": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/cli.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
43
43
  "content": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/content.ts --provider 'anthropic' --name 'Jo' --location 'New York, NY'",
44
44
  "stream": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/stream.ts --provider 'anthropic' --name 'Jo' --location 'New York, NY'",
45
- "code_exec": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/code_exec.ts --provider 'anthropic' --name 'Jo' --location 'New York, NY'",
45
+ "code_exec": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/code_exec.ts --provider 'google' --name 'Jo' --location 'New York, NY'",
46
46
  "image": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/image.ts --provider 'anthropic' --name 'Jo' --location 'New York, NY'",
47
47
  "code_exec_simple": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/code_exec_simple.ts --provider 'anthropic' --name 'Jo' --location 'New York, NY'",
48
- "simple": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/simple.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
48
+ "simple": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/simple.ts --provider 'anthropic' --name 'Jo' --location 'New York, NY'",
49
49
  "memory": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/memory.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
50
50
  "tool-test": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tools.ts --provider 'anthropic' --name 'Jo' --location 'New York, NY'",
51
51
  "abort": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/abort.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
@@ -70,12 +70,13 @@
70
70
  "@aws-crypto/sha256-js": "^5.2.0",
71
71
  "@aws-sdk/credential-provider-node": "^3.613.0",
72
72
  "@aws-sdk/types": "^3.609.0",
73
- "@langchain/anthropic": "^0.3.8",
73
+ "@langchain/anthropic": "^0.3.11",
74
74
  "@langchain/aws": "^0.1.2",
75
75
  "@langchain/community": "^0.3.14",
76
- "@langchain/core": "^0.3.18",
77
- "@langchain/google-vertexai": "^0.1.2",
78
- "@langchain/langgraph": "^0.2.19",
76
+ "@langchain/core": "^0.3.26",
77
+ "@langchain/google-genai": "^0.1.6",
78
+ "@langchain/google-vertexai": "^0.1.5",
79
+ "@langchain/langgraph": "^0.2.34",
79
80
  "@langchain/mistralai": "^0.0.26",
80
81
  "@langchain/ollama": "^0.1.1",
81
82
  "@langchain/openai": "^0.3.14",
@@ -74,6 +74,7 @@ export enum Providers {
74
74
  ANTHROPIC = 'anthropic',
75
75
  MISTRALAI = 'mistralai',
76
76
  OLLAMA = 'ollama',
77
+ GOOGLE = 'google',
77
78
  }
78
79
 
79
80
  export enum GraphNodeKeys {
package/src/events.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  // src/events.ts
3
3
  import type { Graph } from '@/graphs';
4
4
  import type * as t from '@/types';
5
+ import { handleToolCalls } from '@/stream';
6
+ import { Providers } from '@/common';
5
7
 
6
8
  export class HandlerRegistry {
7
9
  private handlers: Map<string, t.EventHandler> = new Map();
@@ -28,6 +30,12 @@ export class ModelEndHandler implements t.EventHandler {
28
30
  console.dir({
29
31
  usage,
30
32
  }, { depth: null });
33
+
34
+ if (metadata.provider !== Providers.GOOGLE) {
35
+ return;
36
+ }
37
+
38
+ handleToolCalls(data?.output?.tool_calls, metadata, graph);
31
39
  }
32
40
  }
33
41
 
@@ -58,7 +66,7 @@ export class TestLLMStreamHandler implements t.EventHandler {
58
66
  handle(event: string, data: t.StreamEventData | undefined): void {
59
67
  const chunk = data?.chunk;
60
68
  const isMessageChunk = !!(chunk && 'message' in chunk);
61
- const msg = isMessageChunk && chunk.message;
69
+ const msg = isMessageChunk ? chunk.message : undefined;
62
70
  if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {
63
71
  console.log(msg.tool_call_chunks);
64
72
  } else if (msg && msg.content) {
@@ -84,7 +84,7 @@ export class CustomAnthropic extends ChatAnthropicMessages {
84
84
  );
85
85
 
86
86
  for await (const data of stream) {
87
- if (options.signal?.aborted) {
87
+ if (options.signal?.aborted === true) {
88
88
  stream.controller.abort();
89
89
  throw new Error('AbortError: User aborted the request.');
90
90
  }
@@ -10,6 +10,7 @@ export type AnthropicToolResponse = {
10
10
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
11
  input: Record<string, any>;
12
12
  };
13
+
13
14
  export type AnthropicMessageParam = Anthropic.MessageParam;
14
15
  export type AnthropicMessageResponse =
15
16
  | Anthropic.ContentBlock
@@ -29,4 +30,9 @@ export type AnthropicToolChoice =
29
30
  | 'auto'
30
31
  | 'none'
31
32
  | string;
32
- export type ChatAnthropicToolType = AnthropicTool | BindToolsInput;
33
+ export type ChatAnthropicToolType = AnthropicTool | BindToolsInput;
34
+ export type AnthropicTextBlockParam = Anthropic.Messages.TextBlockParam;
35
+ export type AnthropicImageBlockParam = Anthropic.Messages.ImageBlockParam;
36
+ export type AnthropicToolUseBlockParam = Anthropic.Messages.ToolUseBlockParam;
37
+ export type AnthropicToolResultBlockParam =
38
+ Anthropic.Messages.ToolResultBlockParam;
@@ -1,19 +1,25 @@
1
+ /* eslint-disable no-console */
1
2
  /**
2
3
  * This util file contains functions for converting LangChain messages to Anthropic messages.
3
4
  */
4
5
  import {
5
- BaseMessage,
6
- SystemMessage,
7
- HumanMessage,
8
6
  AIMessage,
7
+ BaseMessage,
9
8
  ToolMessage,
10
- MessageContent,
11
9
  isAIMessage,
10
+ HumanMessage,
11
+ SystemMessage,
12
+ MessageContent,
12
13
  } from '@langchain/core/messages';
13
14
  import { ToolCall } from '@langchain/core/messages/tool';
14
15
  import type {
15
- AnthropicMessageCreateParams,
16
16
  AnthropicToolResponse,
17
+ AnthropicMessageParam,
18
+ AnthropicTextBlockParam,
19
+ AnthropicImageBlockParam,
20
+ AnthropicToolUseBlockParam,
21
+ AnthropicMessageCreateParams,
22
+ AnthropicToolResultBlockParam,
17
23
  } from '@/llm/anthropic/types';
18
24
 
19
25
  function _formatImage(imageUrl: string): { type: string; media_type: string; data: string } {
@@ -130,7 +136,7 @@ function _formatContent(content: MessageContent): string | Record<string, any>[]
130
136
  ...(cacheControl ? { cache_control: cacheControl } : {}),
131
137
  };
132
138
  } else if (
133
- textTypes.find((t) => t === contentPart.type) &&
139
+ textTypes.find((t) => t === contentPart.type) != null &&
134
140
  'text' in contentPart
135
141
  ) {
136
142
  // Assuming contentPart is of type MessageContentText here
@@ -139,7 +145,7 @@ function _formatContent(content: MessageContent): string | Record<string, any>[]
139
145
  text: contentPart.text,
140
146
  ...(cacheControl ? { cache_control: cacheControl } : {}),
141
147
  };
142
- } else if (toolTypes.find((t) => t === contentPart.type)) {
148
+ } else if (toolTypes.find((t) => t === contentPart.type) != null) {
143
149
  const contentPartCopy = { ...contentPart };
144
150
  if ('index' in contentPartCopy) {
145
151
  // Anthropic does not support passing the index field here, so we remove it.
@@ -252,7 +258,79 @@ export function _convertMessagesToAnthropicPayload(
252
258
  }
253
259
  });
254
260
  return {
255
- messages: formattedMessages,
261
+ messages: mergeMessages(formattedMessages as AnthropicMessageCreateParams['messages']),
256
262
  system,
257
263
  } as AnthropicMessageCreateParams;
264
+ }
265
+
266
+ function mergeMessages(messages?: AnthropicMessageCreateParams['messages']): AnthropicMessageParam[] {
267
+ if (!messages || messages.length <= 1) {
268
+ return messages ?? [];
269
+ }
270
+
271
+ const result: AnthropicMessageCreateParams['messages'] = [];
272
+ let currentMessage = messages[0];
273
+
274
+ const normalizeContent = (
275
+ content:
276
+ | string
277
+ | Array<
278
+ | AnthropicTextBlockParam
279
+ | AnthropicImageBlockParam
280
+ | AnthropicToolUseBlockParam
281
+ | AnthropicToolResultBlockParam
282
+ >
283
+ ): Array<
284
+ | AnthropicTextBlockParam
285
+ | AnthropicImageBlockParam
286
+ | AnthropicToolUseBlockParam
287
+ | AnthropicToolResultBlockParam
288
+ > => {
289
+ if (typeof content === 'string') {
290
+ return [
291
+ {
292
+ type: 'text',
293
+ text: content,
294
+ },
295
+ ];
296
+ }
297
+ return content;
298
+ };
299
+
300
+ const isToolResultMessage = (msg: (typeof messages)[0]): boolean => {
301
+ if (msg.role !== 'user') return false;
302
+
303
+ if (typeof msg.content === 'string') {
304
+ return false;
305
+ }
306
+
307
+ return (
308
+ Array.isArray(msg.content) &&
309
+ msg.content.every((item) => item.type === 'tool_result')
310
+ );
311
+ };
312
+
313
+ for (let i = 1; i < messages.length; i += 1) {
314
+ const nextMessage = messages[i];
315
+
316
+ if (
317
+ isToolResultMessage(currentMessage) &&
318
+ isToolResultMessage(nextMessage)
319
+ ) {
320
+ // Merge the messages by combining their content arrays
321
+ currentMessage = {
322
+ ...currentMessage,
323
+ content: [
324
+ ...normalizeContent(currentMessage.content),
325
+ ...normalizeContent(nextMessage.content),
326
+ ],
327
+ };
328
+ } else {
329
+ result.push(currentMessage);
330
+ currentMessage = nextMessage;
331
+ }
332
+ }
333
+
334
+ result.push(currentMessage);
335
+ return result;
258
336
  }
@@ -2,13 +2,14 @@
2
2
  import { ChatOpenAI } from '@langchain/openai';
3
3
  import { ChatOllama } from '@langchain/ollama';
4
4
  import { ChatBedrockConverse } from '@langchain/aws';
5
- import { ChatAnthropic } from '@langchain/anthropic';
5
+ // import { ChatAnthropic } from '@langchain/anthropic';
6
6
  import { ChatMistralAI } from '@langchain/mistralai';
7
7
  import { ChatVertexAI } from '@langchain/google-vertexai';
8
+ import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
8
9
  import { BedrockChat } from '@langchain/community/chat_models/bedrock/web';
9
10
  import type { ChatModelConstructorMap, ProviderOptionsMap, ChatModelMap } from '@/types';
10
11
  import { Providers } from '@/common';
11
- // import { CustomAnthropic } from '@/llm/anthropic/llm';
12
+ import { CustomAnthropic } from '@/llm/anthropic/llm';
12
13
 
13
14
  export const llmProviders: Partial<ChatModelConstructorMap> = {
14
15
  [Providers.OPENAI]: ChatOpenAI,
@@ -17,8 +18,9 @@ export const llmProviders: Partial<ChatModelConstructorMap> = {
17
18
  [Providers.BEDROCK_LEGACY]: BedrockChat,
18
19
  [Providers.MISTRALAI]: ChatMistralAI,
19
20
  [Providers.BEDROCK]: ChatBedrockConverse,
20
- // [Providers.ANTHROPIC]: CustomAnthropic,
21
- [Providers.ANTHROPIC]: ChatAnthropic,
21
+ [Providers.ANTHROPIC]: CustomAnthropic,
22
+ // [Providers.ANTHROPIC]: ChatAnthropic,
23
+ [Providers.GOOGLE]: ChatGoogleGenerativeAI,
22
24
  };
23
25
 
24
26
  export const manualToolStreamProviders = new Set<Providers | string>([Providers.ANTHROPIC, Providers.BEDROCK, Providers.OLLAMA]);
package/src/llm/text.ts CHANGED
@@ -1,73 +1,52 @@
1
1
  /* eslint-disable no-console */
2
- import { Readable } from 'stream';
3
- import type { ReadableOptions } from 'stream';
4
- export interface TextStreamOptions extends ReadableOptions {
2
+ export interface TextStreamOptions {
5
3
  minChunkSize?: number;
6
4
  maxChunkSize?: number;
7
5
  delay?: number;
6
+ firstWordChunk?: boolean;
8
7
  }
9
8
 
10
9
  export type ProgressCallback = (chunk: string) => void;
11
10
  export type PostChunkCallback = (chunk: string) => void;
12
11
 
13
- export class TextStream extends Readable {
12
+ export class TextStream {
14
13
  private text: string;
15
14
  private currentIndex: number;
16
15
  private minChunkSize: number;
17
16
  private maxChunkSize: number;
18
17
  private delay: number;
18
+ private firstWordChunk: boolean;
19
19
 
20
20
  constructor(text: string, options: TextStreamOptions = {}) {
21
- super(options);
22
21
  this.text = text;
23
22
  this.currentIndex = 0;
24
- this.minChunkSize = options.minChunkSize ?? 2;
25
- this.maxChunkSize = options.maxChunkSize ?? 4;
26
- this.delay = options.delay ?? 20; // Time in milliseconds
27
- }
28
-
29
- _read(): void {
30
- const { delay, minChunkSize, maxChunkSize } = this;
31
-
32
- if (this.currentIndex < this.text.length) {
33
- setTimeout(() => {
34
- const remainingChars = this.text.length - this.currentIndex;
35
- const chunkSize = Math.min(this.randomInt(minChunkSize, maxChunkSize + 1), remainingChars);
36
-
37
- const chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize);
38
- this.push(chunk);
39
- this.currentIndex += chunkSize;
40
- }, delay);
41
- } else {
42
- this.push(null); // signal end of data
43
- }
23
+ this.minChunkSize = options.minChunkSize ?? 4;
24
+ this.maxChunkSize = options.maxChunkSize ?? 8;
25
+ this.delay = options.delay ?? 20;
26
+ this.firstWordChunk = options.firstWordChunk ?? true;
44
27
  }
45
28
 
46
29
  private randomInt(min: number, max: number): number {
47
30
  return Math.floor(Math.random() * (max - min)) + min;
48
31
  }
49
32
 
50
- async processTextStream(progressCallback: ProgressCallback): Promise<void> {
51
- const streamPromise = new Promise<void>((resolve, reject) => {
52
- this.on('data', (chunk) => {
53
- progressCallback(chunk.toString());
54
- });
33
+ private static readonly BOUNDARIES = new Set([' ', '.', ',', '!', '?', ';', ':']);
55
34
 
56
- this.on('end', () => {
57
- resolve();
58
- });
35
+ private findFirstWordBoundary(text: string, minSize: number): number {
36
+ if (minSize >= text.length) return text.length;
59
37
 
60
- this.on('error', (err) => {
61
- reject(err);
62
- });
63
- });
38
+ // Ensure we meet the minimum size first
39
+ let pos = minSize;
64
40
 
65
- try {
66
- await streamPromise;
67
- } catch (err) {
68
- console.error('[processTextStream] Error in text stream:', err);
69
- // Handle the error appropriately, e.g., return an error message or throw an error
41
+ // Look forward until we find a boundary
42
+ while (pos < text.length) {
43
+ if (TextStream.BOUNDARIES.has(text[pos])) {
44
+ return pos + 1; // Include the boundary character
45
+ }
46
+ pos++;
70
47
  }
48
+
49
+ return text.length; // If no boundary found, return entire remaining text
71
50
  }
72
51
 
73
52
  async *generateText(progressCallback?: ProgressCallback): AsyncGenerator<string, void, unknown> {
@@ -76,11 +55,17 @@ export class TextStream extends Readable {
76
55
  while (this.currentIndex < this.text.length) {
77
56
  await new Promise(resolve => setTimeout(resolve, delay));
78
57
 
79
- const remainingChars = this.text.length - this.currentIndex;
80
- const chunkSize = Math.min(this.randomInt(minChunkSize, maxChunkSize + 1), remainingChars);
58
+ const remainingText = this.text.slice(this.currentIndex);
59
+ let chunkSize: number;
81
60
 
82
- const chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize);
61
+ if (this.firstWordChunk) {
62
+ chunkSize = this.findFirstWordBoundary(remainingText, minChunkSize);
63
+ } else {
64
+ const remainingChars = remainingText.length;
65
+ chunkSize = Math.min(this.randomInt(minChunkSize, maxChunkSize + 1), remainingChars);
66
+ }
83
67
 
68
+ const chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize);
84
69
  progressCallback?.(chunk);
85
70
 
86
71
  yield chunk;
package/src/messages.ts CHANGED
@@ -33,7 +33,7 @@ User: ${userMessage[1]}
33
33
 
34
34
  const modifyContent = (messageType: string, content: t.ExtendedMessageContent[]): t.ExtendedMessageContent[] => {
35
35
  return content.map(item => {
36
- if (item && typeof item === 'object' && 'type' in item && item.type) {
36
+ if (item && typeof item === 'object' && 'type' in item && item.type != null && item.type) {
37
37
  let newType = item.type;
38
38
  if (newType.endsWith('_delta')) {
39
39
  newType = newType.replace('_delta', '');
@@ -80,9 +80,9 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
80
80
  formattedContent = message.content.reduce<t.ExtendedMessageContent[]>((acc, item) => {
81
81
  if (typeof item === 'object' && item !== null) {
82
82
  const extendedItem = item as t.ExtendedMessageContent;
83
- if (extendedItem.type === 'text' && extendedItem.text) {
83
+ if (extendedItem.type === 'text' && extendedItem.text != null && extendedItem.text) {
84
84
  acc.push({ type: 'text', text: extendedItem.text });
85
- } else if (extendedItem.type === 'tool_use' && extendedItem.id) {
85
+ } else if (extendedItem.type === 'tool_use' && extendedItem.id != null && extendedItem.id) {
86
86
  const toolCall = toolCallMap.get(extendedItem.id);
87
87
  if (toolCall) {
88
88
  acc.push({
@@ -92,7 +92,7 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
92
92
  input: toolCall.args as unknown as string
93
93
  });
94
94
  }
95
- } else if ('input' in extendedItem && extendedItem.input) {
95
+ } else if ('input' in extendedItem && extendedItem.input != null && extendedItem.input) {
96
96
  try {
97
97
  const parsedInput = JSON.parse(extendedItem.input);
98
98
  const toolCall = message.tool_calls?.find(tc => tc.args.input === parsedInput.input);
@@ -210,14 +210,14 @@ export function convertMessagesToContent(messages: BaseMessage[]): t.MessageCont
210
210
  }
211
211
 
212
212
  export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
213
- const lastMessageY = messages[messages.length - 1];
214
- if (!(lastMessageY instanceof ToolMessage)) return;
213
+ const lastMessage = messages[messages.length - 1];
214
+ if (!(lastMessage instanceof ToolMessage)) return;
215
215
 
216
216
  // Find the latest AIMessage with tool_calls that this tool message belongs to
217
217
  const latestAIParentIndex = findLastIndex(messages,
218
218
  msg => (msg instanceof AIMessageChunk &&
219
219
  (msg.tool_calls?.length ?? 0) > 0 &&
220
- msg.tool_calls?.some(tc => tc.id === lastMessageY.tool_call_id)) ?? false
220
+ msg.tool_calls?.some(tc => tc.id === lastMessage.tool_call_id)) ?? false
221
221
  );
222
222
 
223
223
  if (latestAIParentIndex === -1) return;
@@ -233,70 +233,19 @@ export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
233
233
 
234
234
  if (!hasArtifactContent) return;
235
235
 
236
- // Now we know we need to process these messages
237
236
  const message = messages[latestAIParentIndex] as AIMessageChunk;
238
237
  const toolCallIds = message.tool_calls?.map(tc => tc.id) ?? [];
239
- const toolMessages: ToolMessage[] = [];
240
- let lastToolIndex = latestAIParentIndex;
241
238
 
242
- // Collect all corresponding ToolMessages
243
239
  for (let j = latestAIParentIndex + 1; j < messages.length; j++) {
244
- const potentialToolMessage = messages[j];
245
- if (potentialToolMessage instanceof ToolMessage &&
246
- toolCallIds.includes(potentialToolMessage.tool_call_id)) {
247
-
248
- const hasContentArtifacts = potentialToolMessage.artifact != null &&
249
- Array.isArray(potentialToolMessage.artifact?.content) &&
250
- Array.isArray(potentialToolMessage.content);
251
-
252
- if (hasContentArtifacts) {
253
- potentialToolMessage.content = potentialToolMessage.content.concat(potentialToolMessage.artifact?.content);
254
- }
255
-
256
- toolMessages.push(potentialToolMessage);
257
- lastToolIndex = Math.max(lastToolIndex, j);
240
+ const msg = messages[j];
241
+ if (msg instanceof ToolMessage &&
242
+ toolCallIds.includes(msg.tool_call_id) &&
243
+ msg.artifact != null &&
244
+ Array.isArray(msg.artifact?.content) &&
245
+ Array.isArray(msg.content)) {
246
+ msg.content = msg.content.concat(msg.artifact.content);
258
247
  }
259
248
  }
260
-
261
- // Rest of the function remains the same...
262
- if (toolMessages.length > 1) {
263
- // Keep only first tool call in original AI message
264
- const originalContent = Array.isArray(message.content) ? message.content : [];
265
- message.content = [
266
- originalContent.find(c => c.type === 'text'),
267
- originalContent.find(c => c.type === 'tool_use' && c.id === toolCallIds[0])
268
- ].filter(Boolean) as t.ExtendedMessageContent[];
269
-
270
- message.tool_calls = [message.tool_calls![0]];
271
- if (message.tool_call_chunks) {
272
- message.tool_call_chunks = [message.tool_call_chunks[0]];
273
- }
274
-
275
- const newMessages: BaseMessage[] = [];
276
- newMessages.push(toolMessages[0]);
277
-
278
- // Create new AI+Tool message pairs for remaining tool calls
279
- for (let k = 1; k < toolMessages.length; k++) {
280
- const relevantToolCall = message.lc_kwargs.tool_calls[k];
281
- const relevantToolChunk = message.lc_kwargs.tool_call_chunks?.[k];
282
- const relevantToolUse = originalContent.find(c =>
283
- c.type === 'tool_use' && c.id === toolCallIds[k]
284
- );
285
-
286
- const newAIMessage = new AIMessage({
287
- content: [
288
- originalContent.find(c => c.type === 'text'),
289
- relevantToolUse
290
- ].filter(Boolean),
291
- tool_calls: [relevantToolCall],
292
- tool_call_chunks: relevantToolChunk != null ? [relevantToolChunk] : undefined,
293
- additional_kwargs: { ...message.additional_kwargs }
294
- } as AIMessageChunk);
295
- newMessages.push(newAIMessage, toolMessages[k]);
296
- }
297
-
298
- messages.splice(latestAIParentIndex + 1, lastToolIndex - latestAIParentIndex , ...newMessages);
299
- }
300
249
  }
301
250
 
302
251
  export function formatOpenAIArtifactContent(messages: BaseMessage[]): void {
@@ -20,7 +20,7 @@ export async function getArgs(): Promise<{ userName: string; location: string; p
20
20
  alias: 'p',
21
21
  type: 'string',
22
22
  description: 'LLM provider',
23
- choices: ['openAI', 'anthropic', 'mistralai', 'vertexai', 'bedrock', 'ollama'],
23
+ choices: ['openAI', 'anthropic', 'mistralai', 'vertexai', 'bedrock', 'ollama', 'google'],
24
24
  default: 'openAI'
25
25
  })
26
26
  .help()
@@ -171,6 +171,10 @@ process.on('unhandledRejection', (reason, promise) => {
171
171
  process.exit(1);
172
172
  });
173
173
 
174
+ process.on('uncaughtException', (err) => {
175
+ console.error('Uncaught Exception:', err);
176
+ });
177
+
174
178
  testCodeExecution().catch((err) => {
175
179
  console.error(err);
176
180
  console.log('Conversation history:');
@@ -117,6 +117,10 @@ process.on('unhandledRejection', (reason, promise) => {
117
117
  process.exit(1);
118
118
  });
119
119
 
120
+ process.on('uncaughtException', (err) => {
121
+ console.error('Uncaught Exception:', err);
122
+ });
123
+
120
124
  testStandardStreaming().catch((err) => {
121
125
  console.error(err);
122
126
  console.log('Conversation history:');