@librechat/agents 3.2.46 → 3.2.51

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 (37) hide show
  1. package/dist/cjs/llm/anthropic/index.cjs +4 -3
  2. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/utils/tools.cjs +2 -1
  4. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/cachePoints.cjs +28 -0
  6. package/dist/cjs/llm/bedrock/cachePoints.cjs.map +1 -0
  7. package/dist/cjs/llm/bedrock/index.cjs +10 -1
  8. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  9. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  10. package/dist/esm/llm/anthropic/index.mjs +4 -3
  11. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  12. package/dist/esm/llm/anthropic/utils/tools.mjs +2 -1
  13. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
  14. package/dist/esm/llm/bedrock/cachePoints.mjs +28 -0
  15. package/dist/esm/llm/bedrock/cachePoints.mjs.map +1 -0
  16. package/dist/esm/llm/bedrock/index.mjs +10 -1
  17. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  18. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  19. package/dist/types/llm/anthropic/utils/tools.d.ts +1 -1
  20. package/dist/types/llm/bedrock/cachePoints.d.ts +9 -0
  21. package/package.json +16 -21
  22. package/src/llm/anthropic/index.ts +13 -2
  23. package/src/llm/anthropic/inherited-content-utils.spec.ts +244 -0
  24. package/src/llm/anthropic/inherited-stream-events.spec.ts +752 -0
  25. package/src/llm/anthropic/inherited-strict.spec.ts +302 -0
  26. package/src/llm/anthropic/llm.spec.ts +65 -0
  27. package/src/llm/anthropic/utils/tools.ts +7 -1
  28. package/src/llm/bedrock/cachePoints.ts +86 -0
  29. package/src/llm/bedrock/index.ts +9 -0
  30. package/src/llm/bedrock/inherited-cache.spec.ts +144 -0
  31. package/src/llm/bedrock/inherited.spec.ts +724 -0
  32. package/src/llm/google/inherited-stream-events.spec.ts +350 -0
  33. package/src/llm/openai/inherited-deepseek.spec.ts +347 -0
  34. package/src/llm/openai/inherited-xai.spec.ts +416 -0
  35. package/src/llm/openai/llm.spec.ts +1568 -0
  36. package/src/llm/openrouter/index.ts +1 -3
  37. package/src/llm/vertexai/inherited-stream-events.spec.ts +271 -0
@@ -161,9 +161,7 @@ export class ChatOpenRouter extends ChatOpenAI {
161
161
  return 'LibreChatOpenRouter';
162
162
  }
163
163
 
164
- // @ts-expect-error - OpenRouter reasoning extends OpenAI Reasoning with additional
165
- // effort levels ('xhigh' | 'none' | 'minimal') not in ReasoningEffort.
166
- // The parent's generic conditional return type cannot be widened in an override.
164
+ // OpenRouter widens OpenAI reasoning with extra effort levels ('xhigh' | 'none' | 'minimal').
167
165
  override invocationParams(
168
166
  options?: this['ParsedCallOptions'],
169
167
  extra?: InvocationParamsExtra
@@ -0,0 +1,271 @@
1
+ // Inherited stream-events specs for the LibreChat Vertex fork, ported from two
2
+ // upstream @langchain/google-common@2.2.0 suites:
3
+ //
4
+ // 1. utils/tests/stream_events.test.ts (the original prompt references it as
5
+ // google-common_stream_events.test.ts) — unit tests for the pure converter
6
+ // `convertGoogleGeminiStream`, which turns Gemini-style stream responses
7
+ // into LangChain `ChatModelStreamEvent`s. Our fork's `ChatVertexAI` does
8
+ // not re-implement this converter; it is re-exported from
9
+ // `@langchain/google-common` and exercised verbatim here as a pure unit.
10
+ //
11
+ // 2. chat_models/tests/chat_models_stream_events.test.ts (the prompt references
12
+ // it as google-common_chat_models_stream_events.test.ts) — tests
13
+ // `ChatGoogle.streamEvents()`. Upstream drove a `TestChatGoogle` helper whose
14
+ // `authOptions.resultFile` replayed recorded mock JSON and asserted via
15
+ // vitest custom matchers (`toHaveStreamText` / `toHaveStreamReasoning` /
16
+ // `toHaveStreamToolCalls`). Those fixture files and matchers are not
17
+ // available in this repo, so each case is routed through OUR fork:
18
+ // `new ChatVertexAI({...})` (which extends ChatGoogle and inherits the
19
+ // native `_streamChatModelEvents` path) with the google transport mocked at
20
+ // the `streamedConnection.request` boundary — it returns `{ data: <json
21
+ // stream> }`, the exact shape `_streamChatModelEvents` consumes. The matchers
22
+ // are re-expressed against the public `ChatModelStream` sub-streams
23
+ // (`.text` / `.reasoning` / `.toolCalls`) that those matchers wrap.
24
+ //
25
+ // All cases are deterministic and run against a mocked transport — no live
26
+ // Vertex access. vitest -> jest, `zod/v3` -> n/a (no zod here).
27
+
28
+ import { describe, it, test, expect } from '@jest/globals';
29
+ import { convertGoogleGeminiStream } from '@langchain/google-common';
30
+ import { ChatModelStream } from '@langchain/core/language_models/stream';
31
+ import type { BaseChatModelCallOptions } from '@langchain/core/language_models/chat_models';
32
+ import type { ChatModelStreamEvent } from '@langchain/core/language_models/event';
33
+ import { ChatVertexAI } from '@/llm/vertexai';
34
+
35
+ type GeminiChunk = Record<string, unknown>;
36
+
37
+ // --- Converter unit-test harness (upstream source 1) -----------------------
38
+
39
+ async function* asAsyncIterable(
40
+ chunks: GeminiChunk[]
41
+ ): AsyncGenerator<GeminiChunk> {
42
+ for (const chunk of chunks) {
43
+ yield chunk;
44
+ }
45
+ }
46
+
47
+ async function collectEvents(
48
+ chunks: GeminiChunk[]
49
+ ): Promise<ChatModelStreamEvent[]> {
50
+ const out: ChatModelStreamEvent[] = [];
51
+ for await (const event of convertGoogleGeminiStream(
52
+ asAsyncIterable(chunks)
53
+ )) {
54
+ out.push(event);
55
+ }
56
+ return out;
57
+ }
58
+
59
+ // --- Class streamEvents harness (upstream source 2, routed through fork) ----
60
+
61
+ interface MockJsonStream {
62
+ readonly streamDone: boolean;
63
+ nextChunk(): Promise<GeminiChunk | null>;
64
+ }
65
+
66
+ function makeJsonStream(chunks: GeminiChunk[]): MockJsonStream {
67
+ let i = 0;
68
+ return {
69
+ get streamDone(): boolean {
70
+ return i >= chunks.length;
71
+ },
72
+ async nextChunk(): Promise<GeminiChunk | null> {
73
+ if (i >= chunks.length) return null;
74
+ const chunk = chunks[i];
75
+ i += 1;
76
+ return chunk;
77
+ },
78
+ };
79
+ }
80
+
81
+ interface StreamedConnectionModel {
82
+ streamedConnection: {
83
+ request: () => Promise<{ data: MockJsonStream }>;
84
+ };
85
+ _streamChatModelEvents: (
86
+ messages: unknown[],
87
+ options: BaseChatModelCallOptions
88
+ ) => AsyncGenerator<ChatModelStreamEvent>;
89
+ }
90
+
91
+ function createStreamModel(chunks: GeminiChunk[]): ChatVertexAI {
92
+ const model = new ChatVertexAI({
93
+ model: 'gemini-2.0-flash',
94
+ authOptions: {
95
+ credentials: { client_email: 'test@example.com', private_key: 'test' },
96
+ projectId: 'test-project',
97
+ },
98
+ } as ConstructorParameters<typeof ChatVertexAI>[0]);
99
+ (model as unknown as StreamedConnectionModel).streamedConnection.request =
100
+ async () => ({ data: makeJsonStream(chunks) });
101
+ return model;
102
+ }
103
+
104
+ function streamEvents(
105
+ model: ChatVertexAI,
106
+ options: BaseChatModelCallOptions = {} as BaseChatModelCallOptions
107
+ ): AsyncGenerator<ChatModelStreamEvent> {
108
+ return (model as unknown as StreamedConnectionModel)._streamChatModelEvents(
109
+ [],
110
+ options
111
+ );
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Source 1: convertGoogleGeminiStream (pure converter, re-exported from
116
+ // @langchain/google-common and inherited unchanged by the fork)
117
+ // ---------------------------------------------------------------------------
118
+
119
+ describe('convertGoogleGeminiStream (inherited from @langchain/google-common)', () => {
120
+ test('text-only streaming', async () => {
121
+ const events = await collectEvents([
122
+ { candidates: [{ content: { parts: [{ text: 'Hello' }] } }] },
123
+ { candidates: [{ content: { parts: [{ text: ' world' }] } }] },
124
+ ]);
125
+
126
+ const textDeltas = events.filter(
127
+ (e) =>
128
+ e.event === 'content-block-delta' &&
129
+ (e as { delta: { type: string } }).delta.type === 'text-delta'
130
+ );
131
+ expect(textDeltas).toHaveLength(2);
132
+
133
+ expect(
134
+ events.find((e) => e.event === 'content-block-finish')
135
+ ).toMatchObject({
136
+ content: { text: 'Hello world' },
137
+ });
138
+ });
139
+
140
+ test('maps Gemini finish reasons', async () => {
141
+ const lengthEvents = await collectEvents([
142
+ {
143
+ candidates: [
144
+ {
145
+ content: { parts: [{ text: 'Hello' }] },
146
+ finishReason: 'MAX_TOKENS',
147
+ },
148
+ ],
149
+ },
150
+ ]);
151
+ const lengthFinish = lengthEvents.find((e) => e.event === 'message-finish');
152
+ expect(lengthFinish).toMatchObject({ reason: 'length' });
153
+
154
+ const filterEvents = await collectEvents([
155
+ {
156
+ candidates: [
157
+ {
158
+ content: { parts: [{ text: 'Hello' }] },
159
+ finishReason: 'SAFETY',
160
+ },
161
+ ],
162
+ },
163
+ ]);
164
+ const filterFinish = filterEvents.find((e) => e.event === 'message-finish');
165
+ expect(filterFinish).toMatchObject({ reason: 'content_filter' });
166
+ });
167
+
168
+ test('thinking parts map to reasoning', async () => {
169
+ const events = await collectEvents([
170
+ {
171
+ candidates: [
172
+ {
173
+ content: { parts: [{ text: 'Let me think', thought: true }] },
174
+ },
175
+ ],
176
+ },
177
+ ]);
178
+
179
+ expect(
180
+ events.find(
181
+ (e) =>
182
+ e.event === 'content-block-finish' &&
183
+ (e as { content: { type: string } }).content.type === 'reasoning'
184
+ )
185
+ ).toMatchObject({
186
+ content: { reasoning: 'Let me think' },
187
+ });
188
+ });
189
+
190
+ test('usage snapshots', async () => {
191
+ const events = await collectEvents([
192
+ {
193
+ usageMetadata: {
194
+ promptTokenCount: 10,
195
+ candidatesTokenCount: 4,
196
+ totalTokenCount: 14,
197
+ },
198
+ candidates: [{ content: { parts: [{ text: 'Hi' }] } }],
199
+ },
200
+ ]);
201
+
202
+ expect(events.filter((e) => e.event === 'usage').length).toBe(1);
203
+ });
204
+ });
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // Source 2: ChatGoogle.streamEvents — inherited native streamEvents path on
208
+ // the fork (ChatVertexAI does not override _streamChatModelEvents). Re-expressed
209
+ // against the public ChatModelStream sub-streams that the upstream vitest
210
+ // matchers wrapped.
211
+ // ---------------------------------------------------------------------------
212
+
213
+ describe('ChatVertexAI.streamEvents (inherited native path)', () => {
214
+ test('streams text', async () => {
215
+ const model = createStreamModel([
216
+ { candidates: [{ content: { parts: [{ text: 'Hello' }] } }] },
217
+ {
218
+ candidates: [
219
+ { content: { parts: [{ text: ' world' }] }, finishReason: 'STOP' },
220
+ ],
221
+ },
222
+ ]);
223
+ const stream = new ChatModelStream(streamEvents(model));
224
+ expect(await stream.text).toBe('Hello world');
225
+ });
226
+
227
+ test('streams reasoning', async () => {
228
+ const model = createStreamModel([
229
+ {
230
+ candidates: [
231
+ {
232
+ content: { parts: [{ text: 'Let me reason...', thought: true }] },
233
+ finishReason: 'STOP',
234
+ },
235
+ ],
236
+ },
237
+ ]);
238
+ const stream = new ChatModelStream(streamEvents(model));
239
+ expect(await stream.reasoning).toBe('Let me reason...');
240
+ });
241
+
242
+ test('streams tool calls', async () => {
243
+ const model = createStreamModel([
244
+ {
245
+ candidates: [
246
+ {
247
+ content: {
248
+ parts: [
249
+ {
250
+ functionCall: {
251
+ name: 'web_search',
252
+ args: { query: 'weather' },
253
+ },
254
+ },
255
+ ],
256
+ },
257
+ finishReason: 'STOP',
258
+ },
259
+ ],
260
+ },
261
+ ]);
262
+ const stream = new ChatModelStream(streamEvents(model));
263
+ const calls = await stream.toolCalls;
264
+ expect(calls).toEqual([
265
+ expect.objectContaining({
266
+ name: 'web_search',
267
+ args: { query: 'weather' },
268
+ }),
269
+ ]);
270
+ });
271
+ });