@librechat/agents 3.2.63 → 3.2.65
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.
- package/dist/cjs/graphs/Graph.cjs +3 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +73 -9
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +41 -9
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +3 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs +337 -0
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -0
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +82 -34
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +165 -0
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/crw-search.cjs +105 -0
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +4 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +15 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +3 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +73 -9
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +41 -9
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +3 -2
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/stream_events.mjs +337 -0
- package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -0
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +82 -34
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +163 -0
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/crw-search.mjs +103 -0
- package/dist/esm/tools/search/crw-search.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +4 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +15 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/llm/anthropic/index.d.ts +2 -0
- package/dist/types/llm/anthropic/types.d.ts +2 -0
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -2
- package/dist/types/llm/anthropic/utils/stream_events.d.ts +25 -0
- package/dist/types/tools/search/crw-scraper.d.ts +41 -0
- package/dist/types/tools/search/crw-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +88 -3
- package/package.json +1 -1
- package/src/graphs/Graph.ts +15 -0
- package/src/llm/anthropic/index.ts +134 -10
- package/src/llm/anthropic/inherited-content-utils.spec.ts +10 -5
- package/src/llm/anthropic/inherited-stream-events.spec.ts +513 -9
- package/src/llm/anthropic/llm.spec.ts +100 -16
- package/src/llm/anthropic/types.ts +3 -0
- package/src/llm/anthropic/utils/message_inputs.ts +60 -3
- package/src/llm/anthropic/utils/message_outputs.ts +10 -2
- package/src/llm/anthropic/utils/stream_events.ts +471 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +276 -1
- package/src/llm/bedrock/utils/message_inputs.ts +121 -73
- package/src/tools/search/crw-scraper.ts +244 -0
- package/src/tools/search/crw-search.ts +167 -0
- package/src/tools/search/crw.test.ts +836 -0
- package/src/tools/search/search.ts +7 -1
- package/src/tools/search/tool.ts +23 -3
- package/src/tools/search/types.ts +103 -3
|
@@ -15,7 +15,10 @@ type ConverseResult = ReturnType<typeof convertToConverseMessages>;
|
|
|
15
15
|
/** Minimal view of a converted Bedrock Converse content block the assertions read. */
|
|
16
16
|
interface ConverseBlock {
|
|
17
17
|
text?: string;
|
|
18
|
-
reasoningContent?: {
|
|
18
|
+
reasoningContent?: {
|
|
19
|
+
reasoningText?: { text?: string; signature?: string };
|
|
20
|
+
redactedContent?: Uint8Array;
|
|
21
|
+
};
|
|
19
22
|
toolUse?: {
|
|
20
23
|
toolUseId?: string;
|
|
21
24
|
name?: string;
|
|
@@ -127,3 +130,275 @@ describe('convertToConverseMessages — native Bedrock reasoning serialization',
|
|
|
127
130
|
);
|
|
128
131
|
});
|
|
129
132
|
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Same failure class, v1 converter path. Assistant messages carrying
|
|
136
|
+
* `response_metadata.output_version === 'v1'` are converted by
|
|
137
|
+
* `convertFromV1ToChatBedrockConverseMessage`, which serialized `reasoning` /
|
|
138
|
+
* `reasoning_content` blocks without the null/empty-text guard applied to the
|
|
139
|
+
* non-v1 path — a `reasoning` block whose `reasoning` is null/empty (e.g. a
|
|
140
|
+
* model responding with `thinking.display: "omitted"`, the Opus 4.7+ /
|
|
141
|
+
* Sonnet 5 default) reached Bedrock as `reasoningText: { text: null }` and the
|
|
142
|
+
* whole request was rejected with `Member must not be null`.
|
|
143
|
+
*/
|
|
144
|
+
describe('convertToConverseMessages — v1 reasoning serialization', () => {
|
|
145
|
+
const v1Metadata = {
|
|
146
|
+
output_version: 'v1',
|
|
147
|
+
model_provider: 'anthropic',
|
|
148
|
+
} as const;
|
|
149
|
+
|
|
150
|
+
it('drops a v1 reasoning block whose reasoning text is missing, keeping text and tool calls', () => {
|
|
151
|
+
const messages: BaseMessage[] = [
|
|
152
|
+
new HumanMessage('what data do you have?'),
|
|
153
|
+
new AIMessage({
|
|
154
|
+
content: [
|
|
155
|
+
{ type: 'reasoning', reasoning: undefined } as never,
|
|
156
|
+
{ type: 'text', text: 'Let me check your databases.' },
|
|
157
|
+
],
|
|
158
|
+
tool_calls: [
|
|
159
|
+
{
|
|
160
|
+
id: 'tooluse_list',
|
|
161
|
+
name: 'list_databases',
|
|
162
|
+
args: {},
|
|
163
|
+
type: 'tool_call',
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
response_metadata: v1Metadata,
|
|
167
|
+
}),
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
expect(() => convertToConverseMessages(messages)).not.toThrow();
|
|
171
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
172
|
+
|
|
173
|
+
expect(content.find((b) => b.reasoningContent != null)).toBeUndefined();
|
|
174
|
+
expect(content.some((b) => b.text === 'Let me check your databases.')).toBe(
|
|
175
|
+
true
|
|
176
|
+
);
|
|
177
|
+
const toolUse = content.find((b) => b.toolUse != null);
|
|
178
|
+
expect(toolUse?.toolUse).toMatchObject({
|
|
179
|
+
toolUseId: 'tooluse_list',
|
|
180
|
+
name: 'list_databases',
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('drops a v1 reasoning block whose reasoning text is empty', () => {
|
|
185
|
+
const messages: BaseMessage[] = [
|
|
186
|
+
new HumanMessage('hi'),
|
|
187
|
+
new AIMessage({
|
|
188
|
+
content: [
|
|
189
|
+
{ type: 'reasoning', reasoning: '' },
|
|
190
|
+
{ type: 'text', text: 'answer' },
|
|
191
|
+
],
|
|
192
|
+
response_metadata: v1Metadata,
|
|
193
|
+
}),
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
197
|
+
expect(content.find((b) => b.reasoningContent != null)).toBeUndefined();
|
|
198
|
+
expect(content.some((b) => b.text === 'answer')).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('drops a v1 signature-only reasoning_content block', () => {
|
|
202
|
+
const messages: BaseMessage[] = [
|
|
203
|
+
new HumanMessage('hi'),
|
|
204
|
+
new AIMessage({
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: 'reasoning_content',
|
|
208
|
+
reasoningText: { signature: 'sig-abc' },
|
|
209
|
+
},
|
|
210
|
+
{ type: 'text', text: 'answer' },
|
|
211
|
+
],
|
|
212
|
+
response_metadata: v1Metadata,
|
|
213
|
+
}),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
expect(() => convertToConverseMessages(messages)).not.toThrow();
|
|
217
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
218
|
+
expect(content.find((b) => b.reasoningContent != null)).toBeUndefined();
|
|
219
|
+
expect(JSON.stringify(content)).not.toContain('sig-abc');
|
|
220
|
+
expect(content.some((b) => b.text === 'answer')).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('emits a placeholder (not empty content) when dropping empties a v1 turn', () => {
|
|
224
|
+
const messages: BaseMessage[] = [
|
|
225
|
+
new HumanMessage('hi'),
|
|
226
|
+
new AIMessage({
|
|
227
|
+
content: [{ type: 'reasoning', reasoning: undefined } as never],
|
|
228
|
+
response_metadata: v1Metadata,
|
|
229
|
+
}),
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
expect(() => convertToConverseMessages(messages)).not.toThrow();
|
|
233
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
234
|
+
expect(content.length).toBeGreaterThan(0);
|
|
235
|
+
expect(content.find((b) => b.reasoningContent != null)).toBeUndefined();
|
|
236
|
+
expect(content.every((b) => typeof b.text === 'string')).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it.each(['', ' \n '])(
|
|
240
|
+
'emits a placeholder for invalid v1 text %j',
|
|
241
|
+
(text) => {
|
|
242
|
+
const messages: BaseMessage[] = [
|
|
243
|
+
new HumanMessage('hi'),
|
|
244
|
+
new AIMessage({
|
|
245
|
+
content: [{ type: 'text', text }],
|
|
246
|
+
response_metadata: v1Metadata,
|
|
247
|
+
}),
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
251
|
+
expect(content).toEqual([{ text: '_' }]);
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
it('merges whitespace-only v1 text into the preceding text block', () => {
|
|
256
|
+
const messages: BaseMessage[] = [
|
|
257
|
+
new HumanMessage('hi'),
|
|
258
|
+
new AIMessage({
|
|
259
|
+
content: [
|
|
260
|
+
{ type: 'text', text: 'answer' },
|
|
261
|
+
{ type: 'text', text: ' \n ' },
|
|
262
|
+
],
|
|
263
|
+
response_metadata: v1Metadata,
|
|
264
|
+
}),
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
268
|
+
expect(content).toEqual([{ text: 'answer \n ' }]);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('merges split v1 reasoning_content text and signature blocks before serialization', () => {
|
|
272
|
+
const splitContent = [
|
|
273
|
+
{
|
|
274
|
+
type: 'reasoning_content',
|
|
275
|
+
reasoningText: { text: 'first ' },
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
type: 'reasoning_content',
|
|
279
|
+
reasoningText: { text: 'second' },
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
type: 'reasoning_content',
|
|
283
|
+
reasoningText: { signature: 'sig-abc' },
|
|
284
|
+
},
|
|
285
|
+
{ type: 'text', text: 'answer' },
|
|
286
|
+
];
|
|
287
|
+
const originalContent = structuredClone(splitContent);
|
|
288
|
+
const messages: BaseMessage[] = [
|
|
289
|
+
new HumanMessage('hi'),
|
|
290
|
+
new AIMessage({
|
|
291
|
+
content: splitContent,
|
|
292
|
+
response_metadata: v1Metadata,
|
|
293
|
+
}),
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
297
|
+
const reasoning = content.filter((b) => b.reasoningContent != null);
|
|
298
|
+
expect(reasoning).toHaveLength(1);
|
|
299
|
+
expect(reasoning[0].reasoningContent?.reasoningText).toEqual({
|
|
300
|
+
text: 'first second',
|
|
301
|
+
signature: 'sig-abc',
|
|
302
|
+
});
|
|
303
|
+
expect(splitContent).toEqual(originalContent);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('keeps independently signed v1 reasoning blocks separate', () => {
|
|
307
|
+
const messages: BaseMessage[] = [
|
|
308
|
+
new HumanMessage('hi'),
|
|
309
|
+
new AIMessage({
|
|
310
|
+
content: [
|
|
311
|
+
{
|
|
312
|
+
type: 'reasoning_content',
|
|
313
|
+
reasoningText: { text: 'first', signature: 'sig-first' },
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
type: 'reasoning_content',
|
|
317
|
+
reasoningText: { text: 'second', signature: 'sig-second' },
|
|
318
|
+
},
|
|
319
|
+
{ type: 'text', text: 'answer' },
|
|
320
|
+
],
|
|
321
|
+
response_metadata: v1Metadata,
|
|
322
|
+
}),
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
326
|
+
expect(
|
|
327
|
+
content
|
|
328
|
+
.filter((block) => block.reasoningContent != null)
|
|
329
|
+
.map((block) => block.reasoningContent?.reasoningText)
|
|
330
|
+
).toEqual([
|
|
331
|
+
{ text: 'first', signature: 'sig-first' },
|
|
332
|
+
{ text: 'second', signature: 'sig-second' },
|
|
333
|
+
]);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('keeps adjacent redacted v1 reasoning payloads separate', () => {
|
|
337
|
+
const messages: BaseMessage[] = [
|
|
338
|
+
new HumanMessage('hi'),
|
|
339
|
+
new AIMessage({
|
|
340
|
+
content: [
|
|
341
|
+
{ type: 'reasoning_content', redactedContent: 'YQ==' },
|
|
342
|
+
{ type: 'reasoning_content', redactedContent: 'Yg==' },
|
|
343
|
+
{ type: 'text', text: 'answer' },
|
|
344
|
+
],
|
|
345
|
+
response_metadata: v1Metadata,
|
|
346
|
+
}),
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
350
|
+
const redacted = content
|
|
351
|
+
.map((block) => block.reasoningContent?.redactedContent)
|
|
352
|
+
.filter((value): value is Uint8Array => value != null)
|
|
353
|
+
.map((value) => Buffer.from(value).toString('utf8'));
|
|
354
|
+
expect(redacted).toEqual(['a', 'b']);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('throws instead of returning empty assistant content for an unhandled v1 block', () => {
|
|
358
|
+
const messages: BaseMessage[] = [
|
|
359
|
+
new HumanMessage('hi'),
|
|
360
|
+
new AIMessage({
|
|
361
|
+
content: [
|
|
362
|
+
{
|
|
363
|
+
type: 'image',
|
|
364
|
+
source_type: 'base64',
|
|
365
|
+
data: 'aGVsbG8=',
|
|
366
|
+
mime_type: 'image/png',
|
|
367
|
+
} as never,
|
|
368
|
+
],
|
|
369
|
+
response_metadata: v1Metadata,
|
|
370
|
+
}),
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
expect(() => convertToConverseMessages(messages)).toThrow(
|
|
374
|
+
'Unsupported v1 content block type: image'
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('still converts v1 reasoning and reasoning_content blocks that carry text', () => {
|
|
379
|
+
const messages: BaseMessage[] = [
|
|
380
|
+
new HumanMessage('hi'),
|
|
381
|
+
new AIMessage({
|
|
382
|
+
content: [
|
|
383
|
+
{ type: 'reasoning', reasoning: 'v1 standard reasoning' },
|
|
384
|
+
{
|
|
385
|
+
type: 'reasoning_content',
|
|
386
|
+
reasoningText: { text: 'native reasoning', signature: 'sig' },
|
|
387
|
+
},
|
|
388
|
+
{ type: 'text', text: 'answer' },
|
|
389
|
+
],
|
|
390
|
+
response_metadata: v1Metadata,
|
|
391
|
+
}),
|
|
392
|
+
];
|
|
393
|
+
|
|
394
|
+
const content = assistantContent(convertToConverseMessages(messages));
|
|
395
|
+
const reasoningTexts = content
|
|
396
|
+
.filter((b) => b.reasoningContent != null)
|
|
397
|
+
.map((b) => b.reasoningContent?.reasoningText?.text);
|
|
398
|
+
expect(reasoningTexts).toEqual([
|
|
399
|
+
'v1 standard reasoning',
|
|
400
|
+
'native reasoning',
|
|
401
|
+
]);
|
|
402
|
+
expect(content.some((b) => b.text === 'answer')).toBe(true);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
@@ -88,6 +88,29 @@ function isSerializableBedrockReasoningBlock(
|
|
|
88
88
|
return content.redactedContent != null && content.redactedContent !== '';
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function appendSerializableBedrockTextBlock(
|
|
92
|
+
contentBlocks: BedrockContentBlock[],
|
|
93
|
+
text: string
|
|
94
|
+
): boolean {
|
|
95
|
+
if (text === '') {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const cleanedText = text.replace(/\n/g, '').trim();
|
|
99
|
+
if (cleanedText !== '') {
|
|
100
|
+
contentBlocks.push({ text });
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
const lastBlock = contentBlocks[contentBlocks.length - 1] as
|
|
104
|
+
| BedrockContentBlock
|
|
105
|
+
| undefined;
|
|
106
|
+
if (lastBlock == null || !('text' in lastBlock)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const mergedTextContent = `${lastBlock.text}${text}`;
|
|
110
|
+
(lastBlock as { text: string }).text = mergedTextContent;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
/**
|
|
92
115
|
* Concatenate consecutive reasoning blocks in content array.
|
|
93
116
|
*/
|
|
@@ -96,48 +119,57 @@ export function concatenateLangchainReasoningBlocks(
|
|
|
96
119
|
): Array<MessageContentComplex | MessageContentReasoningBlock> {
|
|
97
120
|
const result: Array<MessageContentComplex | MessageContentReasoningBlock> =
|
|
98
121
|
[];
|
|
122
|
+
let pendingReasoning: MessageContentReasoningBlock | undefined;
|
|
123
|
+
|
|
124
|
+
const flushPendingReasoning = (): void => {
|
|
125
|
+
if (pendingReasoning != null) {
|
|
126
|
+
result.push(pendingReasoning);
|
|
127
|
+
pendingReasoning = undefined;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
99
130
|
|
|
100
131
|
for (const block of content) {
|
|
101
|
-
if (block.type
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
lastText !== '' &&
|
|
120
|
-
currentText != null &&
|
|
121
|
-
currentText !== ''
|
|
122
|
-
) {
|
|
123
|
-
lastReasoning.reasoningText!.text = lastText + currentText;
|
|
124
|
-
} else if (
|
|
125
|
-
currentReasoning.reasoningText.signature != null &&
|
|
126
|
-
currentReasoning.reasoningText.signature !== ''
|
|
127
|
-
) {
|
|
128
|
-
lastReasoning.reasoningText!.signature =
|
|
129
|
-
currentReasoning.reasoningText.signature;
|
|
130
|
-
}
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
132
|
+
if (block.type !== 'reasoning_content') {
|
|
133
|
+
flushPendingReasoning();
|
|
134
|
+
result.push(block);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const currentReasoning = block as MessageContentReasoningBlock;
|
|
139
|
+
if (currentReasoning.reasoningText != null) {
|
|
140
|
+
const previousText = pendingReasoning?.reasoningText?.text;
|
|
141
|
+
const previousSignature = pendingReasoning?.reasoningText?.signature;
|
|
142
|
+
const { text, signature } = currentReasoning.reasoningText;
|
|
143
|
+
const mergedReasoningText: { text?: string; signature?: string } = {};
|
|
144
|
+
if (previousText !== undefined || text !== undefined) {
|
|
145
|
+
mergedReasoningText.text = (previousText ?? '') + (text ?? '');
|
|
146
|
+
}
|
|
147
|
+
if (previousSignature !== undefined || signature !== undefined) {
|
|
148
|
+
mergedReasoningText.signature =
|
|
149
|
+
(previousSignature ?? '') + (signature ?? '');
|
|
133
150
|
}
|
|
151
|
+
pendingReasoning = {
|
|
152
|
+
type: 'reasoning_content',
|
|
153
|
+
reasoningText: mergedReasoningText,
|
|
154
|
+
};
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
156
|
+
// A signature seals the accumulated reasoning text. Any following
|
|
157
|
+
// reasoning block starts a new independently signed Bedrock block.
|
|
158
|
+
if ('signature' in currentReasoning.reasoningText) {
|
|
159
|
+
flushPendingReasoning();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (currentReasoning.redactedContent != null) {
|
|
164
|
+
flushPendingReasoning();
|
|
165
|
+
result.push({
|
|
166
|
+
type: 'reasoning_content',
|
|
167
|
+
redactedContent: currentReasoning.redactedContent,
|
|
168
|
+
});
|
|
138
169
|
}
|
|
139
170
|
}
|
|
140
171
|
|
|
172
|
+
flushPendingReasoning();
|
|
141
173
|
return result;
|
|
142
174
|
}
|
|
143
175
|
|
|
@@ -666,23 +698,7 @@ function convertAIMessageToConverseMessage(msg: BaseMessage): BedrockMessage {
|
|
|
666
698
|
concatenatedBlocks.forEach((block) => {
|
|
667
699
|
if (block.type === 'text') {
|
|
668
700
|
const text = (block as { text?: string }).text ?? '';
|
|
669
|
-
|
|
670
|
-
if (text === '') {
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
// Merge whitespace/newlines with previous text blocks to avoid validation errors.
|
|
674
|
-
const cleanedText = text.replace(/\n/g, '').trim();
|
|
675
|
-
if (cleanedText === '') {
|
|
676
|
-
if (contentBlocks.length > 0) {
|
|
677
|
-
const lastBlock = contentBlocks[contentBlocks.length - 1];
|
|
678
|
-
if ('text' in lastBlock) {
|
|
679
|
-
const mergedTextContent = `${lastBlock.text}${text}`;
|
|
680
|
-
(lastBlock as { text: string }).text = mergedTextContent;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
} else {
|
|
684
|
-
contentBlocks.push({ text });
|
|
685
|
-
}
|
|
701
|
+
appendSerializableBedrockTextBlock(contentBlocks, text);
|
|
686
702
|
} else if (block.type === 'reasoning_content') {
|
|
687
703
|
const reasoningBlock = block as MessageContentReasoningBlock;
|
|
688
704
|
// Bedrock Converse rejects reasoningContent whose reasoningText.text is
|
|
@@ -757,26 +773,35 @@ function convertAIMessageToConverseMessage(msg: BaseMessage): BedrockMessage {
|
|
|
757
773
|
function convertFromV1ToChatBedrockConverseMessage(
|
|
758
774
|
msg: BaseMessage
|
|
759
775
|
): BedrockMessage {
|
|
776
|
+
const contentBlocks: BedrockContentBlock[] = [];
|
|
760
777
|
const assistantMsg: BedrockMessage = {
|
|
761
778
|
role: 'assistant',
|
|
762
|
-
content:
|
|
779
|
+
content: contentBlocks,
|
|
763
780
|
};
|
|
781
|
+
let droppedUnserializableContent = false;
|
|
782
|
+
let unconvertedContentType: string | undefined;
|
|
764
783
|
|
|
765
784
|
if (Array.isArray(msg.content)) {
|
|
766
|
-
|
|
767
|
-
MessageContentComplex | MessageContentReasoningBlock
|
|
768
|
-
|
|
785
|
+
const concatenatedBlocks = concatenateLangchainReasoningBlocks(
|
|
786
|
+
msg.content as Array<MessageContentComplex | MessageContentReasoningBlock>
|
|
787
|
+
);
|
|
788
|
+
for (const block of concatenatedBlocks) {
|
|
769
789
|
if (typeof block === 'string') {
|
|
770
|
-
|
|
790
|
+
if (!appendSerializableBedrockTextBlock(contentBlocks, block)) {
|
|
791
|
+
droppedUnserializableContent = true;
|
|
792
|
+
}
|
|
771
793
|
} else if (block.type === 'text') {
|
|
772
|
-
|
|
794
|
+
const text = (block as { text?: string }).text ?? '';
|
|
795
|
+
if (!appendSerializableBedrockTextBlock(contentBlocks, text)) {
|
|
796
|
+
droppedUnserializableContent = true;
|
|
797
|
+
}
|
|
773
798
|
} else if (block.type === 'tool_call') {
|
|
774
799
|
const toolCall = block as {
|
|
775
800
|
id: string;
|
|
776
801
|
name: string;
|
|
777
802
|
args: Record<string, unknown>;
|
|
778
803
|
};
|
|
779
|
-
|
|
804
|
+
contentBlocks.push({
|
|
780
805
|
toolUse: {
|
|
781
806
|
toolUseId: toolCall.id,
|
|
782
807
|
name: toolCall.name,
|
|
@@ -784,38 +809,51 @@ function convertFromV1ToChatBedrockConverseMessage(
|
|
|
784
809
|
},
|
|
785
810
|
} as BedrockContentBlock);
|
|
786
811
|
} else if (block.type === 'reasoning') {
|
|
787
|
-
const reasoning = block as { reasoning
|
|
788
|
-
|
|
812
|
+
const reasoning = block as { reasoning?: string };
|
|
813
|
+
/** Bedrock Converse rejects `reasoningText` with a null/empty `text`
|
|
814
|
+
* (`Member must not be null`), e.g. when the producing model omitted
|
|
815
|
+
* reasoning text (`thinking.display: "omitted"`) — drop rather than send. */
|
|
816
|
+
if (reasoning.reasoning == null || reasoning.reasoning === '') {
|
|
817
|
+
droppedUnserializableContent = true;
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
contentBlocks.push({
|
|
789
821
|
reasoningContent: {
|
|
790
822
|
reasoningText: { text: reasoning.reasoning },
|
|
791
823
|
},
|
|
792
824
|
} as BedrockContentBlock);
|
|
793
825
|
} else if (block.type === 'reasoning_content') {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
826
|
+
const reasoningBlock = block as MessageContentReasoningBlock;
|
|
827
|
+
if (!isSerializableBedrockReasoningBlock(reasoningBlock)) {
|
|
828
|
+
droppedUnserializableContent = true;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
contentBlocks.push({
|
|
832
|
+
reasoningContent:
|
|
833
|
+
langchainReasoningBlockToBedrockReasoningBlock(reasoningBlock),
|
|
798
834
|
} as BedrockContentBlock);
|
|
835
|
+
} else {
|
|
836
|
+
unconvertedContentType ??= block.type;
|
|
799
837
|
}
|
|
800
838
|
}
|
|
801
|
-
} else if (typeof msg.content === 'string'
|
|
802
|
-
|
|
839
|
+
} else if (typeof msg.content === 'string') {
|
|
840
|
+
if (!appendSerializableBedrockTextBlock(contentBlocks, msg.content)) {
|
|
841
|
+
droppedUnserializableContent = true;
|
|
842
|
+
}
|
|
803
843
|
}
|
|
804
844
|
|
|
805
845
|
// Also handle tool_calls from the message
|
|
806
846
|
if (isAIMessage(msg) && msg.tool_calls != null && msg.tool_calls.length > 0) {
|
|
807
847
|
// Check if tool calls are already in content
|
|
808
848
|
const existingToolUseIds = new Set(
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
.map(
|
|
812
|
-
(c) => (c as { toolUse: { toolUseId: string } }).toolUse.toolUseId
|
|
813
|
-
) ?? []
|
|
849
|
+
contentBlocks
|
|
850
|
+
.filter((c) => 'toolUse' in c)
|
|
851
|
+
.map((c) => (c as { toolUse: { toolUseId: string } }).toolUse.toolUseId)
|
|
814
852
|
);
|
|
815
853
|
|
|
816
854
|
for (const tc of msg.tool_calls) {
|
|
817
855
|
if (!existingToolUseIds.has(tc.id ?? '')) {
|
|
818
|
-
|
|
856
|
+
contentBlocks.push({
|
|
819
857
|
toolUse: {
|
|
820
858
|
toolUseId: tc.id,
|
|
821
859
|
name: tc.name,
|
|
@@ -826,6 +864,16 @@ function convertFromV1ToChatBedrockConverseMessage(
|
|
|
826
864
|
}
|
|
827
865
|
}
|
|
828
866
|
|
|
867
|
+
const hasNoContent = contentBlocks.length === 0;
|
|
868
|
+
if (hasNoContent && unconvertedContentType != null) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
`Unsupported v1 content block type: ${unconvertedContentType}`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
if (hasNoContent && droppedUnserializableContent) {
|
|
874
|
+
contentBlocks.push({ text: BEDROCK_EMPTY_TEXT_PLACEHOLDER });
|
|
875
|
+
}
|
|
876
|
+
|
|
829
877
|
return assistantMsg;
|
|
830
878
|
}
|
|
831
879
|
|