@librechat/agents 2.4.322 → 3.0.0-rc1

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 (258) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +218 -0
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -0
  3. package/dist/cjs/common/enum.cjs +14 -5
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +10 -6
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +309 -212
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs +322 -0
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -0
  11. package/dist/cjs/llm/anthropic/index.cjs +54 -9
  12. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  13. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  14. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +52 -6
  15. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  16. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +22 -2
  17. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  18. package/dist/cjs/llm/anthropic/utils/tools.cjs +29 -0
  19. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -0
  20. package/dist/cjs/llm/google/index.cjs +144 -0
  21. package/dist/cjs/llm/google/index.cjs.map +1 -0
  22. package/dist/cjs/llm/google/utils/common.cjs +477 -0
  23. package/dist/cjs/llm/google/utils/common.cjs.map +1 -0
  24. package/dist/cjs/llm/ollama/index.cjs +67 -0
  25. package/dist/cjs/llm/ollama/index.cjs.map +1 -0
  26. package/dist/cjs/llm/ollama/utils.cjs +158 -0
  27. package/dist/cjs/llm/ollama/utils.cjs.map +1 -0
  28. package/dist/cjs/llm/openai/index.cjs +389 -3
  29. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  30. package/dist/cjs/llm/openai/utils/index.cjs +672 -0
  31. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -0
  32. package/dist/cjs/llm/providers.cjs +15 -15
  33. package/dist/cjs/llm/providers.cjs.map +1 -1
  34. package/dist/cjs/llm/text.cjs +14 -3
  35. package/dist/cjs/llm/text.cjs.map +1 -1
  36. package/dist/cjs/llm/vertexai/index.cjs +330 -0
  37. package/dist/cjs/llm/vertexai/index.cjs.map +1 -0
  38. package/dist/cjs/main.cjs +11 -0
  39. package/dist/cjs/main.cjs.map +1 -1
  40. package/dist/cjs/run.cjs +120 -81
  41. package/dist/cjs/run.cjs.map +1 -1
  42. package/dist/cjs/stream.cjs +85 -51
  43. package/dist/cjs/stream.cjs.map +1 -1
  44. package/dist/cjs/tools/ToolNode.cjs +10 -4
  45. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  46. package/dist/cjs/tools/handlers.cjs +119 -13
  47. package/dist/cjs/tools/handlers.cjs.map +1 -1
  48. package/dist/cjs/tools/search/anthropic.cjs +40 -0
  49. package/dist/cjs/tools/search/anthropic.cjs.map +1 -0
  50. package/dist/cjs/tools/search/firecrawl.cjs +55 -9
  51. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  52. package/dist/cjs/tools/search/format.cjs +6 -6
  53. package/dist/cjs/tools/search/format.cjs.map +1 -1
  54. package/dist/cjs/tools/search/rerankers.cjs +7 -29
  55. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  56. package/dist/cjs/tools/search/search.cjs +86 -16
  57. package/dist/cjs/tools/search/search.cjs.map +1 -1
  58. package/dist/cjs/tools/search/tool.cjs +4 -2
  59. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  60. package/dist/cjs/tools/search/utils.cjs +1 -1
  61. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  62. package/dist/cjs/utils/events.cjs +31 -0
  63. package/dist/cjs/utils/events.cjs.map +1 -0
  64. package/dist/cjs/utils/title.cjs +57 -21
  65. package/dist/cjs/utils/title.cjs.map +1 -1
  66. package/dist/cjs/utils/tokens.cjs +54 -7
  67. package/dist/cjs/utils/tokens.cjs.map +1 -1
  68. package/dist/esm/agents/AgentContext.mjs +216 -0
  69. package/dist/esm/agents/AgentContext.mjs.map +1 -0
  70. package/dist/esm/common/enum.mjs +15 -6
  71. package/dist/esm/common/enum.mjs.map +1 -1
  72. package/dist/esm/events.mjs +10 -6
  73. package/dist/esm/events.mjs.map +1 -1
  74. package/dist/esm/graphs/Graph.mjs +311 -214
  75. package/dist/esm/graphs/Graph.mjs.map +1 -1
  76. package/dist/esm/graphs/MultiAgentGraph.mjs +320 -0
  77. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -0
  78. package/dist/esm/llm/anthropic/index.mjs +54 -9
  79. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  80. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  81. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +52 -6
  82. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  83. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +22 -2
  84. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  85. package/dist/esm/llm/anthropic/utils/tools.mjs +27 -0
  86. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -0
  87. package/dist/esm/llm/google/index.mjs +142 -0
  88. package/dist/esm/llm/google/index.mjs.map +1 -0
  89. package/dist/esm/llm/google/utils/common.mjs +471 -0
  90. package/dist/esm/llm/google/utils/common.mjs.map +1 -0
  91. package/dist/esm/llm/ollama/index.mjs +65 -0
  92. package/dist/esm/llm/ollama/index.mjs.map +1 -0
  93. package/dist/esm/llm/ollama/utils.mjs +155 -0
  94. package/dist/esm/llm/ollama/utils.mjs.map +1 -0
  95. package/dist/esm/llm/openai/index.mjs +388 -4
  96. package/dist/esm/llm/openai/index.mjs.map +1 -1
  97. package/dist/esm/llm/openai/utils/index.mjs +666 -0
  98. package/dist/esm/llm/openai/utils/index.mjs.map +1 -0
  99. package/dist/esm/llm/providers.mjs +5 -5
  100. package/dist/esm/llm/providers.mjs.map +1 -1
  101. package/dist/esm/llm/text.mjs +14 -3
  102. package/dist/esm/llm/text.mjs.map +1 -1
  103. package/dist/esm/llm/vertexai/index.mjs +328 -0
  104. package/dist/esm/llm/vertexai/index.mjs.map +1 -0
  105. package/dist/esm/main.mjs +6 -5
  106. package/dist/esm/main.mjs.map +1 -1
  107. package/dist/esm/run.mjs +121 -83
  108. package/dist/esm/run.mjs.map +1 -1
  109. package/dist/esm/stream.mjs +87 -54
  110. package/dist/esm/stream.mjs.map +1 -1
  111. package/dist/esm/tools/ToolNode.mjs +10 -4
  112. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  113. package/dist/esm/tools/handlers.mjs +119 -15
  114. package/dist/esm/tools/handlers.mjs.map +1 -1
  115. package/dist/esm/tools/search/anthropic.mjs +37 -0
  116. package/dist/esm/tools/search/anthropic.mjs.map +1 -0
  117. package/dist/esm/tools/search/firecrawl.mjs +55 -9
  118. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  119. package/dist/esm/tools/search/format.mjs +7 -7
  120. package/dist/esm/tools/search/format.mjs.map +1 -1
  121. package/dist/esm/tools/search/rerankers.mjs +7 -29
  122. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  123. package/dist/esm/tools/search/search.mjs +86 -16
  124. package/dist/esm/tools/search/search.mjs.map +1 -1
  125. package/dist/esm/tools/search/tool.mjs +4 -2
  126. package/dist/esm/tools/search/tool.mjs.map +1 -1
  127. package/dist/esm/tools/search/utils.mjs +1 -1
  128. package/dist/esm/tools/search/utils.mjs.map +1 -1
  129. package/dist/esm/utils/events.mjs +29 -0
  130. package/dist/esm/utils/events.mjs.map +1 -0
  131. package/dist/esm/utils/title.mjs +57 -22
  132. package/dist/esm/utils/title.mjs.map +1 -1
  133. package/dist/esm/utils/tokens.mjs +54 -8
  134. package/dist/esm/utils/tokens.mjs.map +1 -1
  135. package/dist/types/agents/AgentContext.d.ts +91 -0
  136. package/dist/types/common/enum.d.ts +15 -6
  137. package/dist/types/events.d.ts +5 -4
  138. package/dist/types/graphs/Graph.d.ts +64 -67
  139. package/dist/types/graphs/MultiAgentGraph.d.ts +37 -0
  140. package/dist/types/graphs/index.d.ts +1 -0
  141. package/dist/types/llm/anthropic/index.d.ts +11 -0
  142. package/dist/types/llm/anthropic/types.d.ts +9 -3
  143. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
  144. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +4 -4
  145. package/dist/types/llm/anthropic/utils/tools.d.ts +3 -0
  146. package/dist/types/llm/google/index.d.ts +13 -0
  147. package/dist/types/llm/google/types.d.ts +32 -0
  148. package/dist/types/llm/google/utils/common.d.ts +19 -0
  149. package/dist/types/llm/google/utils/tools.d.ts +10 -0
  150. package/dist/types/llm/google/utils/zod_to_genai_parameters.d.ts +14 -0
  151. package/dist/types/llm/ollama/index.d.ts +7 -0
  152. package/dist/types/llm/ollama/utils.d.ts +7 -0
  153. package/dist/types/llm/openai/index.d.ts +72 -3
  154. package/dist/types/llm/openai/types.d.ts +10 -0
  155. package/dist/types/llm/openai/utils/index.d.ts +20 -0
  156. package/dist/types/llm/text.d.ts +1 -1
  157. package/dist/types/llm/vertexai/index.d.ts +293 -0
  158. package/dist/types/messages/reducer.d.ts +9 -0
  159. package/dist/types/run.d.ts +19 -12
  160. package/dist/types/scripts/ant_web_search.d.ts +1 -0
  161. package/dist/types/scripts/args.d.ts +2 -1
  162. package/dist/types/scripts/handoff-test.d.ts +1 -0
  163. package/dist/types/scripts/multi-agent-conditional.d.ts +1 -0
  164. package/dist/types/scripts/multi-agent-parallel.d.ts +1 -0
  165. package/dist/types/scripts/multi-agent-sequence.d.ts +1 -0
  166. package/dist/types/scripts/multi-agent-test.d.ts +1 -0
  167. package/dist/types/stream.d.ts +10 -3
  168. package/dist/types/tools/CodeExecutor.d.ts +2 -2
  169. package/dist/types/tools/ToolNode.d.ts +1 -1
  170. package/dist/types/tools/handlers.d.ts +17 -4
  171. package/dist/types/tools/search/anthropic.d.ts +16 -0
  172. package/dist/types/tools/search/firecrawl.d.ts +15 -0
  173. package/dist/types/tools/search/rerankers.d.ts +0 -1
  174. package/dist/types/tools/search/types.d.ts +30 -9
  175. package/dist/types/types/graph.d.ts +95 -15
  176. package/dist/types/types/llm.d.ts +24 -10
  177. package/dist/types/types/run.d.ts +46 -8
  178. package/dist/types/types/stream.d.ts +16 -2
  179. package/dist/types/types/tools.d.ts +1 -1
  180. package/dist/types/utils/events.d.ts +6 -0
  181. package/dist/types/utils/title.d.ts +2 -1
  182. package/dist/types/utils/tokens.d.ts +24 -0
  183. package/package.json +33 -17
  184. package/src/agents/AgentContext.ts +315 -0
  185. package/src/common/enum.ts +14 -5
  186. package/src/events.ts +24 -13
  187. package/src/graphs/Graph.ts +495 -312
  188. package/src/graphs/MultiAgentGraph.ts +381 -0
  189. package/src/graphs/index.ts +2 -1
  190. package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
  191. package/src/llm/anthropic/index.ts +78 -13
  192. package/src/llm/anthropic/llm.spec.ts +491 -115
  193. package/src/llm/anthropic/types.ts +39 -3
  194. package/src/llm/anthropic/utils/message_inputs.ts +67 -11
  195. package/src/llm/anthropic/utils/message_outputs.ts +21 -2
  196. package/src/llm/anthropic/utils/output_parsers.ts +25 -6
  197. package/src/llm/anthropic/utils/tools.ts +29 -0
  198. package/src/llm/google/index.ts +218 -0
  199. package/src/llm/google/types.ts +43 -0
  200. package/src/llm/google/utils/common.ts +646 -0
  201. package/src/llm/google/utils/tools.ts +160 -0
  202. package/src/llm/google/utils/zod_to_genai_parameters.ts +86 -0
  203. package/src/llm/ollama/index.ts +89 -0
  204. package/src/llm/ollama/utils.ts +193 -0
  205. package/src/llm/openai/index.ts +600 -14
  206. package/src/llm/openai/types.ts +24 -0
  207. package/src/llm/openai/utils/index.ts +912 -0
  208. package/src/llm/openai/utils/isReasoningModel.test.ts +90 -0
  209. package/src/llm/providers.ts +10 -9
  210. package/src/llm/text.ts +26 -7
  211. package/src/llm/vertexai/index.ts +360 -0
  212. package/src/messages/reducer.ts +80 -0
  213. package/src/run.ts +181 -112
  214. package/src/scripts/ant_web_search.ts +158 -0
  215. package/src/scripts/args.ts +12 -8
  216. package/src/scripts/cli4.ts +29 -21
  217. package/src/scripts/cli5.ts +29 -21
  218. package/src/scripts/code_exec.ts +54 -23
  219. package/src/scripts/code_exec_files.ts +48 -17
  220. package/src/scripts/code_exec_simple.ts +46 -27
  221. package/src/scripts/handoff-test.ts +135 -0
  222. package/src/scripts/image.ts +52 -20
  223. package/src/scripts/multi-agent-conditional.ts +220 -0
  224. package/src/scripts/multi-agent-example-output.md +110 -0
  225. package/src/scripts/multi-agent-parallel.ts +337 -0
  226. package/src/scripts/multi-agent-sequence.ts +212 -0
  227. package/src/scripts/multi-agent-test.ts +186 -0
  228. package/src/scripts/search.ts +1 -9
  229. package/src/scripts/simple.ts +25 -10
  230. package/src/scripts/tools.ts +48 -18
  231. package/src/specs/anthropic.simple.test.ts +150 -34
  232. package/src/specs/azure.simple.test.ts +325 -0
  233. package/src/specs/openai.simple.test.ts +140 -33
  234. package/src/specs/openrouter.simple.test.ts +107 -0
  235. package/src/specs/prune.test.ts +4 -9
  236. package/src/specs/reasoning.test.ts +80 -44
  237. package/src/specs/token-memoization.test.ts +39 -0
  238. package/src/stream.test.ts +94 -0
  239. package/src/stream.ts +139 -60
  240. package/src/tools/ToolNode.ts +21 -7
  241. package/src/tools/handlers.ts +192 -18
  242. package/src/tools/search/anthropic.ts +51 -0
  243. package/src/tools/search/firecrawl.ts +69 -20
  244. package/src/tools/search/format.ts +6 -8
  245. package/src/tools/search/rerankers.ts +7 -40
  246. package/src/tools/search/search.ts +97 -16
  247. package/src/tools/search/tool.ts +5 -2
  248. package/src/tools/search/types.ts +30 -10
  249. package/src/tools/search/utils.ts +1 -1
  250. package/src/types/graph.ts +272 -103
  251. package/src/types/llm.ts +25 -12
  252. package/src/types/run.ts +51 -13
  253. package/src/types/stream.ts +22 -1
  254. package/src/types/tools.ts +16 -10
  255. package/src/utils/events.ts +32 -0
  256. package/src/utils/llmConfig.ts +19 -7
  257. package/src/utils/title.ts +104 -30
  258. package/src/utils/tokens.ts +69 -10
@@ -4,12 +4,19 @@
4
4
  import { config } from 'dotenv';
5
5
  config();
6
6
  import { Calculator } from '@langchain/community/tools/calculator';
7
- import { HumanMessage, BaseMessage, UsageMetadata } from '@langchain/core/messages';
8
- import type { StandardGraph } from '@/graphs';
7
+ import {
8
+ HumanMessage,
9
+ BaseMessage,
10
+ UsageMetadata,
11
+ } from '@langchain/core/messages';
9
12
  import type * as t from '@/types';
10
- import { ToolEndHandler, ModelEndHandler, createMetadataAggregator } from '@/events';
13
+ import {
14
+ ToolEndHandler,
15
+ ModelEndHandler,
16
+ createMetadataAggregator,
17
+ } from '@/events';
18
+ import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
11
19
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
12
- import { ContentTypes, GraphEvents, Providers } from '@/common';
13
20
  import { capitalizeFirstLetter } from './spec.utils';
14
21
  import { getLLMConfig } from '@/utils/llmConfig';
15
22
  import { getArgs } from '@/scripts/args';
@@ -36,7 +43,8 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
36
43
  beforeEach(async () => {
37
44
  conversationHistory = [];
38
45
  collectedUsage = [];
39
- const { contentParts: cp, aggregateContent: ac } = createContentAggregator();
46
+ const { contentParts: cp, aggregateContent: ac } =
47
+ createContentAggregator();
40
48
  contentParts = cp as t.MessageContentComplex[];
41
49
  aggregateContent = ac;
42
50
  });
@@ -49,36 +57,62 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
49
57
  onRunStepSpy.mockReset();
50
58
  });
51
59
 
52
- const setupCustomHandlers = (): Record<string | GraphEvents, t.EventHandler> => ({
60
+ const setupCustomHandlers = (): Record<
61
+ string | GraphEvents,
62
+ t.EventHandler
63
+ > => ({
53
64
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
54
65
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
55
66
  [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
56
67
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
57
- handle: (event: GraphEvents.ON_RUN_STEP_COMPLETED, data: t.StreamEventData): void => {
58
- aggregateContent({ event, data: data as unknown as { result: t.ToolEndEvent; } });
59
- }
68
+ handle: (
69
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
70
+ data: t.StreamEventData
71
+ ): void => {
72
+ aggregateContent({
73
+ event,
74
+ data: data as unknown as { result: t.ToolEndEvent },
75
+ });
76
+ },
60
77
  },
61
78
  [GraphEvents.ON_RUN_STEP]: {
62
- handle: (event: GraphEvents.ON_RUN_STEP, data: t.StreamEventData, metadata, graph): void => {
79
+ handle: (
80
+ event: GraphEvents.ON_RUN_STEP,
81
+ data: t.StreamEventData,
82
+ metadata,
83
+ graph
84
+ ): void => {
63
85
  onRunStepSpy(event, data, metadata, graph);
64
86
  aggregateContent({ event, data: data as t.RunStep });
65
- }
87
+ },
66
88
  },
67
89
  [GraphEvents.ON_RUN_STEP_DELTA]: {
68
- handle: (event: GraphEvents.ON_RUN_STEP_DELTA, data: t.StreamEventData): void => {
90
+ handle: (
91
+ event: GraphEvents.ON_RUN_STEP_DELTA,
92
+ data: t.StreamEventData
93
+ ): void => {
69
94
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
70
- }
95
+ },
71
96
  },
72
97
  [GraphEvents.ON_MESSAGE_DELTA]: {
73
- handle: (event: GraphEvents.ON_MESSAGE_DELTA, data: t.StreamEventData, metadata, graph): void => {
98
+ handle: (
99
+ event: GraphEvents.ON_MESSAGE_DELTA,
100
+ data: t.StreamEventData,
101
+ metadata,
102
+ graph
103
+ ): void => {
74
104
  onMessageDeltaSpy(event, data, metadata, graph);
75
105
  aggregateContent({ event, data: data as t.MessageDeltaEvent });
76
- }
106
+ },
77
107
  },
78
108
  [GraphEvents.TOOL_START]: {
79
- handle: (_event: string, _data: t.StreamEventData, _metadata?: Record<string, unknown>): void => {
109
+ handle: (
110
+ _event: string,
111
+ _data: t.StreamEventData,
112
+ _metadata?: Record<string, unknown>
113
+ ): void => {
80
114
  // Handle tool start
81
- }
115
+ },
82
116
  },
83
117
  });
84
118
 
@@ -93,7 +127,8 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
93
127
  type: 'standard',
94
128
  llmConfig,
95
129
  tools: [new Calculator()],
96
- instructions: 'You are a friendly AI assistant. Always address the user by their name.',
130
+ instructions:
131
+ 'You are a friendly AI assistant. Always address the user by their name.',
97
132
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
98
133
  },
99
134
  returnContent: true,
@@ -109,7 +144,9 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
109
144
 
110
145
  const finalContentParts = await run.processStream(inputs, config);
111
146
  expect(finalContentParts).toBeDefined();
112
- const allTextParts = finalContentParts?.every((part) => part.type === ContentTypes.TEXT);
147
+ const allTextParts = finalContentParts?.every(
148
+ (part) => part.type === ContentTypes.TEXT
149
+ );
113
150
  expect(allTextParts).toBe(true);
114
151
  expect(collectedUsage.length).toBeGreaterThan(0);
115
152
  expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
@@ -117,26 +154,30 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
117
154
 
118
155
  const finalMessages = run.getRunMessages();
119
156
  expect(finalMessages).toBeDefined();
120
- conversationHistory.push(...finalMessages ?? []);
157
+ conversationHistory.push(...(finalMessages ?? []));
121
158
  expect(conversationHistory.length).toBeGreaterThan(1);
122
159
  runningHistory = conversationHistory.slice();
123
160
 
124
161
  expect(onMessageDeltaSpy).toHaveBeenCalled();
125
162
  expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
126
- expect((onMessageDeltaSpy.mock.calls[0][3] as StandardGraph).provider).toBeDefined();
163
+ expect(onMessageDeltaSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
127
164
 
128
165
  expect(onRunStepSpy).toHaveBeenCalled();
129
166
  expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
130
- expect((onRunStepSpy.mock.calls[0][3] as StandardGraph).provider).toBeDefined();
167
+ expect(onRunStepSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
131
168
 
132
169
  const { handleLLMEnd, collected } = createMetadataAggregator();
133
170
  const titleResult = await run.generateTitle({
171
+ provider,
134
172
  inputText: userMessage,
173
+ titleMethod: TitleMethod.STRUCTURED,
135
174
  contentParts,
136
175
  chainOptions: {
137
- callbacks: [{
138
- handleLLMEnd,
139
- }],
176
+ callbacks: [
177
+ {
178
+ handleLLMEnd,
179
+ },
180
+ ],
140
181
  },
141
182
  });
142
183
 
@@ -146,9 +187,66 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
146
187
  expect(collected).toBeDefined();
147
188
  });
148
189
 
190
+ test(`${capitalizeFirstLetter(provider)}: should generate title using completion method`, async () => {
191
+ const { userName, location } = await getArgs();
192
+ const llmConfig = getLLMConfig(provider);
193
+ const customHandlers = setupCustomHandlers();
194
+
195
+ run = await Run.create<t.IState>({
196
+ runId: 'test-run-id-completion',
197
+ graphConfig: {
198
+ type: 'standard',
199
+ llmConfig,
200
+ tools: [new Calculator()],
201
+ instructions:
202
+ 'You are a friendly AI assistant. Always address the user by their name.',
203
+ additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
204
+ },
205
+ returnContent: true,
206
+ customHandlers,
207
+ });
208
+
209
+ const userMessage = 'What is the weather like today?';
210
+ conversationHistory = [];
211
+ conversationHistory.push(new HumanMessage(userMessage));
212
+
213
+ const inputs = {
214
+ messages: conversationHistory,
215
+ };
216
+
217
+ const finalContentParts = await run.processStream(inputs, config);
218
+ expect(finalContentParts).toBeDefined();
219
+
220
+ const { handleLLMEnd, collected } = createMetadataAggregator();
221
+ const titleResult = await run.generateTitle({
222
+ provider,
223
+ inputText: userMessage,
224
+ titleMethod: TitleMethod.COMPLETION, // Using completion method
225
+ contentParts,
226
+ chainOptions: {
227
+ callbacks: [
228
+ {
229
+ handleLLMEnd,
230
+ },
231
+ ],
232
+ },
233
+ });
234
+
235
+ expect(titleResult).toBeDefined();
236
+ expect(titleResult.title).toBeDefined();
237
+ expect(titleResult.title).not.toBe('');
238
+ // Completion method doesn't return language
239
+ expect(titleResult.language).toBeUndefined();
240
+ expect(collected).toBeDefined();
241
+ console.log(`Completion method generated title: "${titleResult.title}"`);
242
+ });
243
+
149
244
  test(`${capitalizeFirstLetter(provider)}: should follow-up`, async () => {
150
245
  console.log('Previous conversation length:', runningHistory.length);
151
- console.log('Last message:', runningHistory[runningHistory.length - 1].content);
246
+ console.log(
247
+ 'Last message:',
248
+ runningHistory[runningHistory.length - 1].content
249
+ );
152
250
  const { userName, location } = await getArgs();
153
251
  const llmConfig = getLLMConfig(provider);
154
252
  const customHandlers = setupCustomHandlers();
@@ -159,7 +257,8 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
159
257
  type: 'standard',
160
258
  llmConfig,
161
259
  tools: [new Calculator()],
162
- instructions: 'You are a friendly AI assistant. Always address the user by their name.',
260
+ instructions:
261
+ 'You are a friendly AI assistant. Always address the user by their name.',
163
262
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
164
263
  },
165
264
  returnContent: true,
@@ -175,7 +274,9 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
175
274
 
176
275
  const finalContentParts = await run.processStream(inputs, config);
177
276
  expect(finalContentParts).toBeDefined();
178
- const allTextParts = finalContentParts?.every((part) => part.type === ContentTypes.TEXT);
277
+ const allTextParts = finalContentParts?.every(
278
+ (part) => part.type === ContentTypes.TEXT
279
+ );
179
280
  expect(allTextParts).toBe(true);
180
281
  expect(collectedUsage.length).toBeGreaterThan(0);
181
282
  expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
@@ -184,7 +285,10 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
184
285
  const finalMessages = run.getRunMessages();
185
286
  expect(finalMessages).toBeDefined();
186
287
  expect(finalMessages?.length).toBeGreaterThan(0);
187
- console.log(`${capitalizeFirstLetter(provider)} follow-up message:`, finalMessages?.[finalMessages.length - 1]?.content);
288
+ console.log(
289
+ `${capitalizeFirstLetter(provider)} follow-up message:`,
290
+ finalMessages?.[finalMessages.length - 1]?.content
291
+ );
188
292
 
189
293
  expect(onMessageDeltaSpy).toHaveBeenCalled();
190
294
  expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
@@ -196,9 +300,12 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
196
300
  test('should handle errors appropriately', async () => {
197
301
  // Test error scenarios
198
302
  await expect(async () => {
199
- await run.processStream({
200
- messages: [],
201
- }, {} as any);
303
+ await run.processStream(
304
+ {
305
+ messages: [],
306
+ },
307
+ {} as any
308
+ );
202
309
  }).rejects.toThrow();
203
310
  });
204
- });
311
+ });
@@ -0,0 +1,107 @@
1
+ import { config } from 'dotenv';
2
+ config();
3
+ import { Calculator } from '@langchain/community/tools/calculator';
4
+ import {
5
+ HumanMessage,
6
+ BaseMessage,
7
+ UsageMetadata,
8
+ } from '@langchain/core/messages';
9
+ import type * as t from '@/types';
10
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
11
+ import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
12
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
13
+ import { capitalizeFirstLetter } from './spec.utils';
14
+ import { getLLMConfig } from '@/utils/llmConfig';
15
+ import { getArgs } from '@/scripts/args';
16
+ import { Run } from '@/run';
17
+
18
+ // Auto-skip if OpenRouter env is missing
19
+ const hasOpenRouter = (process.env.OPENROUTER_API_KEY ?? '').trim() !== '';
20
+ const describeIf = hasOpenRouter ? describe : describe.skip;
21
+
22
+ const provider = Providers.OPENROUTER;
23
+ describeIf(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
24
+ jest.setTimeout(60000);
25
+ let run: Run<t.IState>;
26
+ let collectedUsage: UsageMetadata[];
27
+ let conversationHistory: BaseMessage[];
28
+ let contentParts: t.MessageContentComplex[];
29
+
30
+ const configV2 = {
31
+ configurable: { thread_id: 'or-convo-1' },
32
+ streamMode: 'values',
33
+ version: 'v2' as const,
34
+ };
35
+
36
+ beforeEach(async () => {
37
+ conversationHistory = [];
38
+ collectedUsage = [];
39
+ const { contentParts: cp } = createContentAggregator();
40
+ contentParts = cp as t.MessageContentComplex[];
41
+ });
42
+
43
+ const onMessageDeltaSpy = jest.fn();
44
+ const onRunStepSpy = jest.fn();
45
+
46
+ afterAll(() => {
47
+ onMessageDeltaSpy.mockReset();
48
+ onRunStepSpy.mockReset();
49
+ });
50
+
51
+ const setupCustomHandlers = (): Record<
52
+ string | GraphEvents,
53
+ t.EventHandler
54
+ > => ({
55
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
56
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
57
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
58
+ });
59
+
60
+ test(`${capitalizeFirstLetter(provider)}: simple stream + title`, async () => {
61
+ const { userName, location } = await getArgs();
62
+ const llmConfig = getLLMConfig(provider);
63
+ const customHandlers = setupCustomHandlers();
64
+
65
+ run = await Run.create<t.IState>({
66
+ runId: 'or-run-1',
67
+ graphConfig: {
68
+ type: 'standard',
69
+ llmConfig,
70
+ tools: [new Calculator()],
71
+ instructions: 'You are a friendly AI assistant.',
72
+ additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
73
+ },
74
+ returnContent: true,
75
+ customHandlers,
76
+ });
77
+
78
+ const userMessage = 'hi';
79
+ conversationHistory.push(new HumanMessage(userMessage));
80
+
81
+ const finalContentParts = await run.processStream(
82
+ { messages: conversationHistory },
83
+ configV2
84
+ );
85
+ expect(finalContentParts).toBeDefined();
86
+ const allTextParts = finalContentParts?.every(
87
+ (part) => part.type === ContentTypes.TEXT
88
+ );
89
+ expect(allTextParts).toBe(true);
90
+ expect(
91
+ (collectedUsage[0]?.input_tokens ?? 0) +
92
+ (collectedUsage[0]?.output_tokens ?? 0)
93
+ ).toBeGreaterThan(0);
94
+
95
+ const finalMessages = run.getRunMessages();
96
+ expect(finalMessages).toBeDefined();
97
+ conversationHistory.push(...(finalMessages ?? []));
98
+
99
+ const titleRes = await run.generateTitle({
100
+ provider,
101
+ inputText: userMessage,
102
+ titleMethod: TitleMethod.COMPLETION,
103
+ contentParts,
104
+ });
105
+ expect(titleRes.title).toBeDefined();
106
+ });
107
+ });
@@ -725,8 +725,11 @@ describe('Prune Messages Tests', () => {
725
725
  type: 'standard',
726
726
  llmConfig,
727
727
  instructions: 'You are a helpful assistant.',
728
+ maxContextTokens: 1000,
728
729
  },
729
730
  returnContent: true,
731
+ tokenCounter,
732
+ indexTokenCountMap: {},
730
733
  });
731
734
 
732
735
  // Override the model to use a fake LLM
@@ -734,10 +737,6 @@ describe('Prune Messages Tests', () => {
734
737
 
735
738
  const messages = [new HumanMessage('Hello, how are you?')];
736
739
 
737
- const indexTokenCountMap = {
738
- 0: tokenCounter(messages[0]),
739
- };
740
-
741
740
  const config: Partial<RunnableConfig> & {
742
741
  version: 'v1' | 'v2';
743
742
  streamMode: string;
@@ -749,11 +748,7 @@ describe('Prune Messages Tests', () => {
749
748
  version: 'v2' as const,
750
749
  };
751
750
 
752
- await run.processStream({ messages }, config, {
753
- maxContextTokens: 1000,
754
- indexTokenCountMap,
755
- tokenCounter,
756
- });
751
+ await run.processStream({ messages }, config);
757
752
 
758
753
  const finalMessages = run.getRunMessages();
759
754
  expect(finalMessages).toBeDefined();
@@ -3,9 +3,12 @@
3
3
  // src/scripts/cli.test.ts
4
4
  import { config } from 'dotenv';
5
5
  config();
6
- import { HumanMessage, BaseMessage, MessageContentText } from '@langchain/core/messages';
6
+ import {
7
+ HumanMessage,
8
+ BaseMessage,
9
+ MessageContentText,
10
+ } from '@langchain/core/messages';
7
11
  import type { RunnableConfig } from '@langchain/core/runnables';
8
- import type { StandardGraph } from '@/graphs';
9
12
  import type * as t from '@/types';
10
13
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
11
14
  import { capitalizeFirstLetter } from './spec.utils';
@@ -28,40 +31,49 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
28
31
  let aggregateContent: t.ContentAggregator;
29
32
  let runSteps: Set<string>;
30
33
 
31
- const config: Partial<RunnableConfig> & { version: 'v1' | 'v2'; run_id?: string; streamMode: string } = {
34
+ const config: Partial<RunnableConfig> & {
35
+ version: 'v1' | 'v2';
36
+ run_id?: string;
37
+ streamMode: string;
38
+ } = {
32
39
  configurable: {
33
40
  thread_id: 'conversation-num-1',
34
41
  },
35
42
  streamMode: 'values',
36
43
  version: 'v2' as const,
37
- callbacks: [{
38
- async handleCustomEvent(event, data, metadata): Promise<void> {
39
- if (event !== GraphEvents.ON_MESSAGE_DELTA) {
40
- return;
41
- }
42
- const messageDeltaData = data as t.MessageDeltaEvent;
43
-
44
- // Wait until we see the run step (with timeout for safety)
45
- const maxAttempts = 50; // 5 seconds total
46
- let attempts = 0;
47
- while (!runSteps.has(messageDeltaData.id) && attempts < maxAttempts) {
48
- await new Promise(resolve => setTimeout(resolve, 100));
49
- attempts++;
50
- }
51
-
52
- if (!runSteps.has(messageDeltaData.id)) {
53
- console.warn(`Timeout waiting for run step: ${messageDeltaData.id}`);
54
- }
55
-
56
- onMessageDeltaSpy(event, data, metadata, run.Graph);
57
- aggregateContent({ event, data: messageDeltaData });
44
+ callbacks: [
45
+ {
46
+ async handleCustomEvent(event, data, metadata): Promise<void> {
47
+ if (event !== GraphEvents.ON_MESSAGE_DELTA) {
48
+ return;
49
+ }
50
+ const messageDeltaData = data as t.MessageDeltaEvent;
51
+
52
+ // Wait until we see the run step (with timeout for safety)
53
+ const maxAttempts = 50; // 5 seconds total
54
+ let attempts = 0;
55
+ while (!runSteps.has(messageDeltaData.id) && attempts < maxAttempts) {
56
+ await new Promise((resolve) => setTimeout(resolve, 100));
57
+ attempts++;
58
+ }
59
+
60
+ if (!runSteps.has(messageDeltaData.id)) {
61
+ console.warn(
62
+ `Timeout waiting for run step: ${messageDeltaData.id}`
63
+ );
64
+ }
65
+
66
+ onMessageDeltaSpy(event, data, metadata, run.Graph);
67
+ aggregateContent({ event, data: messageDeltaData });
68
+ },
58
69
  },
59
- }],
70
+ ],
60
71
  };
61
72
 
62
73
  beforeEach(async () => {
63
74
  conversationHistory = [];
64
- const { contentParts: parts, aggregateContent: ac } = createContentAggregator();
75
+ const { contentParts: parts, aggregateContent: ac } =
76
+ createContentAggregator();
65
77
  aggregateContent = ac;
66
78
  runSteps = new Set();
67
79
  contentParts = parts as t.MessageContentComplex[];
@@ -81,32 +93,54 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
81
93
  onRunStepSpy.mockReset();
82
94
  });
83
95
 
84
- const setupCustomHandlers = (): Record<string | GraphEvents, t.EventHandler> => ({
96
+ const setupCustomHandlers = (): Record<
97
+ string | GraphEvents,
98
+ t.EventHandler
99
+ > => ({
85
100
  [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
86
101
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
87
- handle: (event: GraphEvents.ON_RUN_STEP_COMPLETED, data: t.StreamEventData): void => {
88
- aggregateContent({ event, data: data as unknown as { result: t.ToolEndEvent; } });
89
- }
102
+ handle: (
103
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
104
+ data: t.StreamEventData
105
+ ): void => {
106
+ aggregateContent({
107
+ event,
108
+ data: data as unknown as { result: t.ToolEndEvent },
109
+ });
110
+ },
90
111
  },
91
112
  [GraphEvents.ON_RUN_STEP]: {
92
- handle: (event: GraphEvents.ON_RUN_STEP, data: t.StreamEventData, metadata, graph): void => {
113
+ handle: (
114
+ event: GraphEvents.ON_RUN_STEP,
115
+ data: t.StreamEventData,
116
+ metadata,
117
+ graph
118
+ ): void => {
93
119
  const runStepData = data as t.RunStep;
94
120
  runSteps.add(runStepData.id);
95
121
 
96
122
  onRunStepSpy(event, runStepData, metadata, graph);
97
123
  aggregateContent({ event, data: runStepData });
98
- }
124
+ },
99
125
  },
100
126
  [GraphEvents.ON_RUN_STEP_DELTA]: {
101
- handle: (event: GraphEvents.ON_RUN_STEP_DELTA, data: t.StreamEventData): void => {
127
+ handle: (
128
+ event: GraphEvents.ON_RUN_STEP_DELTA,
129
+ data: t.StreamEventData
130
+ ): void => {
102
131
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
103
- }
132
+ },
104
133
  },
105
134
  [GraphEvents.ON_REASONING_DELTA]: {
106
- handle: (event: GraphEvents.ON_REASONING_DELTA, data: t.StreamEventData, metadata, graph): void => {
135
+ handle: (
136
+ event: GraphEvents.ON_REASONING_DELTA,
137
+ data: t.StreamEventData,
138
+ metadata,
139
+ graph
140
+ ): void => {
107
141
  onReasoningDeltaSpy(event, data, metadata, graph);
108
142
  aggregateContent({ event, data: data as t.ReasoningDeltaEvent });
109
- }
143
+ },
110
144
  },
111
145
  });
112
146
 
@@ -120,7 +154,8 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
120
154
  graphConfig: {
121
155
  type: 'standard',
122
156
  llmConfig,
123
- instructions: 'You are a friendly AI assistant. Always address the user by their name.',
157
+ instructions:
158
+ 'You are a friendly AI assistant. Always address the user by their name.',
124
159
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
125
160
  },
126
161
  returnContent: true,
@@ -141,25 +176,26 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
141
176
  expect(contentParts.length).toBe(2);
142
177
  const reasoningContent = reasoningText.match(/<think>(.*)<\/think>/s)?.[0];
143
178
  const content = reasoningText.split(/<\/think>/)[1];
144
- expect((contentParts[0] as t.ReasoningContentText).think).toBe(reasoningContent);
179
+ expect((contentParts[0] as t.ReasoningContentText).think).toBe(
180
+ reasoningContent
181
+ );
145
182
  expect((contentParts[1] as MessageContentText).text).toBe(content);
146
183
 
147
184
  const finalMessages = run.getRunMessages();
148
185
  expect(finalMessages).toBeDefined();
149
- conversationHistory.push(...finalMessages ?? []);
186
+ conversationHistory.push(...(finalMessages ?? []));
150
187
  expect(conversationHistory.length).toBeGreaterThan(1);
151
188
 
152
189
  expect(onMessageDeltaSpy).toHaveBeenCalled();
153
190
  expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
154
- expect((onMessageDeltaSpy.mock.calls[0][3] as StandardGraph).provider).toBeDefined();
191
+ expect(onMessageDeltaSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
155
192
 
156
193
  expect(onReasoningDeltaSpy).toHaveBeenCalled();
157
194
  expect(onReasoningDeltaSpy.mock.calls.length).toBeGreaterThan(1);
158
- expect((onReasoningDeltaSpy.mock.calls[0][3] as StandardGraph).provider).toBeDefined();
195
+ expect(onReasoningDeltaSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
159
196
 
160
197
  expect(onRunStepSpy).toHaveBeenCalled();
161
198
  expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
162
- expect((onRunStepSpy.mock.calls[0][3] as StandardGraph).provider).toBeDefined();
163
-
199
+ expect(onRunStepSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
164
200
  });
165
- });
201
+ });
@@ -0,0 +1,39 @@
1
+ import { HumanMessage } from '@langchain/core/messages';
2
+ import { createTokenCounter } from '@/utils/tokens';
3
+
4
+ describe('Token encoder memoization', () => {
5
+ jest.setTimeout(45000);
6
+
7
+ test('fetches BPE once and reuses encoder across counters', async () => {
8
+ const originalFetch = global.fetch;
9
+ let fetchCalls = 0;
10
+ global.fetch = (async (...args: Parameters<typeof fetch>) => {
11
+ fetchCalls += 1;
12
+ // Delegate to real fetch
13
+ return await originalFetch(...args);
14
+ }) as typeof fetch;
15
+
16
+ try {
17
+ const counter1 = await createTokenCounter();
18
+ const counter2 = await createTokenCounter();
19
+
20
+ const m1 = new HumanMessage('hello world');
21
+ const m2 = new HumanMessage('another short text');
22
+
23
+ const c11 = counter1(m1);
24
+ const c12 = counter1(m2);
25
+ const c21 = counter2(m1);
26
+ const c22 = counter2(m2);
27
+
28
+ expect(c11).toBeGreaterThan(0);
29
+ expect(c12).toBeGreaterThan(0);
30
+ expect(c21).toBe(c11);
31
+ expect(c22).toBe(c12);
32
+
33
+ // Only one fetch for the shared encoder
34
+ expect(fetchCalls).toBe(1);
35
+ } finally {
36
+ global.fetch = originalFetch;
37
+ }
38
+ });
39
+ });