@tanstack/ai-bedrock 0.1.4 → 0.1.5
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/esm/adapters/converse-text.js +404 -420
- package/dist/esm/adapters/converse-text.js.map +1 -1
- package/dist/esm/adapters/responses-text.js +24 -18
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/dist/esm/adapters/text.js +41 -32
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/converse/message-converter.js +112 -158
- package/dist/esm/converse/message-converter.js.map +1 -1
- package/dist/esm/converse/stream-processor.js +220 -198
- package/dist/esm/converse/stream-processor.js.map +1 -1
- package/dist/esm/converse/structured-output.js +19 -18
- package/dist/esm/converse/structured-output.js.map +1 -1
- package/dist/esm/converse/tool-converter.js +20 -17
- package/dist/esm/converse/tool-converter.js.map +1 -1
- package/dist/esm/index.js +48 -49
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model-catalog.generated.js +170 -89
- package/dist/esm/model-catalog.generated.js.map +1 -1
- package/dist/esm/model-meta.js +9 -13
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/utils/auth.js +33 -43
- package/dist/esm/utils/auth.js.map +1 -1
- package/dist/esm/utils/client.js +30 -29
- package/dist/esm/utils/client.js.map +1 -1
- package/dist/esm/utils/openai-sigv4-fetch.js +36 -31
- package/dist/esm/utils/openai-sigv4-fetch.js.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-converter.js","sources":["../../../src/converse/message-converter.ts"],"sourcesContent":["import { normalizeSystemPrompts } from '@tanstack/ai'\nimport type {\n ContentPart,\n ContentPartDataSource,\n DocumentPart,\n ImagePart,\n ModelMessage,\n SystemPrompt,\n TextPart,\n} from '@tanstack/ai'\nimport type {\n ContentBlock,\n Message,\n SystemContentBlock,\n ToolResultContentBlock,\n} from '@aws-sdk/client-bedrock-runtime'\nimport type { DocumentType } from '@smithy/types'\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction base64ToBytes(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'))\n}\n\nfunction imageFormat(mime: string): 'png' | 'jpeg' | 'gif' | 'webp' {\n switch (mime) {\n case 'image/png':\n return 'png'\n case 'image/jpeg':\n case 'image/jpg':\n return 'jpeg'\n case 'image/gif':\n return 'gif'\n case 'image/webp':\n return 'webp'\n default:\n throw new Error(\n `Bedrock Converse: unsupported image MIME type \"${mime}\". Supported types: image/png, image/jpeg, image/gif, image/webp.`,\n )\n }\n}\n\nfunction documentFormat(\n mime: string,\n): 'pdf' | 'csv' | 'doc' | 'docx' | 'xls' | 'xlsx' | 'html' | 'txt' | 'md' {\n switch (mime) {\n case 'application/pdf':\n return 'pdf'\n case 'text/csv':\n return 'csv'\n case 'application/msword':\n return 'doc'\n case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':\n return 'docx'\n case 'application/vnd.ms-excel':\n return 'xls'\n case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':\n return 'xlsx'\n case 'text/html':\n return 'html'\n case 'text/plain':\n return 'txt'\n case 'text/markdown':\n case 'text/x-markdown':\n return 'md'\n default:\n throw new Error(\n `Bedrock Converse: unsupported document MIME type \"${mime}\". Supported types: pdf, csv, doc, docx, xls, xlsx, html, txt, md.`,\n )\n }\n}\n\nfunction stringContent(content: string | null | Array<ContentPart>): string {\n if (content === null) return ''\n if (typeof content === 'string') return content\n return content\n .filter((p): p is TextPart => p.type === 'text')\n .map((p) => p.content)\n .join('')\n}\n\nfunction isTextPart(p: ContentPart): p is TextPart {\n return p.type === 'text'\n}\n\nfunction isImagePart(p: ContentPart): p is ImagePart {\n return p.type === 'image'\n}\n\nfunction isDocumentPart(p: ContentPart): p is DocumentPart {\n return p.type === 'document'\n}\n\nfunction isDataSource(\n source: ImagePart['source'] | DocumentPart['source'],\n): source is ContentPartDataSource {\n return source.type === 'data'\n}\n\nfunction contentPartToBlock(part: ContentPart, docIndex: number): ContentBlock {\n if (isTextPart(part)) {\n return { text: part.content }\n }\n\n if (isImagePart(part)) {\n const { source } = part\n if (!isDataSource(source)) {\n throw new Error(\n 'Bedrock Converse requires inline image bytes; URL image sources are not supported.',\n )\n }\n return {\n image: {\n format: imageFormat(source.mimeType),\n source: { bytes: base64ToBytes(source.value) },\n },\n }\n }\n\n if (isDocumentPart(part)) {\n const { source } = part\n if (!isDataSource(source)) {\n throw new Error(\n 'Bedrock Converse requires inline document bytes; URL document sources are not supported.',\n )\n }\n return {\n document: {\n format: documentFormat(source.mimeType),\n name: `document-${docIndex}`,\n source: { bytes: base64ToBytes(source.value) },\n },\n }\n }\n\n // Fail loud for unsupported part types (audio, video, etc.)\n throw new Error(\n `Bedrock Converse does not support content part type \"${String(part.type)}\".`,\n )\n}\n\nfunction messageToBlocks(\n msg: ModelMessage,\n docCounter: { value: number },\n): Array<ContentBlock> {\n const blocks: Array<ContentBlock> = []\n\n if (msg.role === 'tool') {\n if (!msg.toolCallId) {\n throw new Error(\n 'Bedrock Converse: tool message is missing toolCallId. Every tool result must reference the tool use ID it is responding to.',\n )\n }\n const textContent = stringContent(msg.content)\n const toolResult: ToolResultContentBlock = { text: textContent }\n blocks.push({\n toolResult: {\n toolUseId: msg.toolCallId,\n content: [toolResult],\n status: 'success',\n },\n })\n return blocks\n }\n\n // Map content field to text/image/document blocks\n if (typeof msg.content === 'string') {\n if (msg.content !== '') {\n blocks.push({ text: msg.content })\n }\n } else if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n const docIndex = isDocumentPart(part) ? ++docCounter.value : 0\n blocks.push(contentPartToBlock(part, docIndex))\n }\n }\n // null → no text blocks\n\n // Append toolUse blocks for assistant tool calls. Malformed or non-object\n // arguments come from a prior assistant turn the engine already accepted, so\n // they signal a real upstream problem — throw rather than silently coercing to\n // `{}` and forwarding a corrupted tool call (the adapter's catch surfaces it\n // as a RUN_ERROR).\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const call of msg.toolCalls) {\n const rawArguments = call.function.arguments || '{}'\n let parsed: unknown\n try {\n parsed = JSON.parse(rawArguments)\n } catch (error: unknown) {\n throw new Error(\n `Bedrock Converse: tool call \"${call.function.name}\" has malformed JSON arguments (${String(error)}). Raw: ${rawArguments}`,\n )\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n throw new Error(\n `Bedrock Converse: tool call \"${call.function.name}\" arguments must be a JSON object, got ${Array.isArray(parsed) ? 'array' : typeof parsed}.`,\n )\n }\n blocks.push({\n toolUse: {\n toolUseId: call.id,\n name: call.function.name,\n input: parsed as DocumentType,\n },\n })\n }\n }\n\n return blocks\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Convert TanStack AI messages + system prompts into the Converse API format.\n *\n * - System prompts are lifted into `SystemContentBlock[]`.\n * - `tool` role messages are remapped to `user` role `toolResult` blocks.\n * - Consecutive messages with the same Converse role are merged (Converse\n * requires strict user/assistant alternation).\n */\nexport function toConverseMessages(\n messages: Array<ModelMessage>,\n systemPrompts?: Array<SystemPrompt>,\n): { system: Array<SystemContentBlock>; messages: Array<Message> } {\n // Build system blocks (uses normalizeSystemPrompts for runtime validation)\n const system: Array<SystemContentBlock> = normalizeSystemPrompts(\n systemPrompts,\n ).map((p) => ({ text: p.content }))\n\n // Convert each ModelMessage to a Converse Message, merging same-role pairs\n const converseMessages: Array<Message> = []\n // Global document counter: ensures every document block across all messages\n // gets a unique name, preventing Bedrock ValidationException for duplicate names.\n const docCounter = { value: 0 }\n\n for (const msg of messages) {\n // Map TanStack roles to Converse roles\n const converseRole: 'user' | 'assistant' =\n msg.role === 'assistant' ? 'assistant' : 'user'\n\n const blocks = messageToBlocks(msg, docCounter)\n\n // Skip messages that produce no content blocks (e.g. assistant with\n // null content and no toolCalls). Pushing an empty-content message to\n // Converse triggers a ValidationException.\n if (blocks.length === 0) continue\n\n const last = converseMessages[converseMessages.length - 1]\n if (last && last.role === converseRole) {\n // Merge into the previous message's content array\n last.content = [...(last.content ?? []), ...blocks]\n } else {\n converseMessages.push({ role: converseRole, content: blocks })\n }\n }\n\n return { system, messages: converseMessages }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message-converter.js","names":[],"sources":["../../../src/converse/message-converter.ts"],"sourcesContent":["import { normalizeSystemPrompts } from '@tanstack/ai'\nimport type {\n ContentPart,\n ContentPartDataSource,\n DocumentPart,\n ImagePart,\n ModelMessage,\n SystemPrompt,\n TextPart,\n} from '@tanstack/ai'\nimport type {\n ContentBlock,\n Message,\n SystemContentBlock,\n ToolResultContentBlock,\n} from '@aws-sdk/client-bedrock-runtime'\nimport type { DocumentType } from '@smithy/types'\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction base64ToBytes(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'))\n}\n\nfunction imageFormat(mime: string): 'png' | 'jpeg' | 'gif' | 'webp' {\n switch (mime) {\n case 'image/png':\n return 'png'\n case 'image/jpeg':\n case 'image/jpg':\n return 'jpeg'\n case 'image/gif':\n return 'gif'\n case 'image/webp':\n return 'webp'\n default:\n throw new Error(\n `Bedrock Converse: unsupported image MIME type \"${mime}\". Supported types: image/png, image/jpeg, image/gif, image/webp.`,\n )\n }\n}\n\nfunction documentFormat(\n mime: string,\n): 'pdf' | 'csv' | 'doc' | 'docx' | 'xls' | 'xlsx' | 'html' | 'txt' | 'md' {\n switch (mime) {\n case 'application/pdf':\n return 'pdf'\n case 'text/csv':\n return 'csv'\n case 'application/msword':\n return 'doc'\n case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':\n return 'docx'\n case 'application/vnd.ms-excel':\n return 'xls'\n case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':\n return 'xlsx'\n case 'text/html':\n return 'html'\n case 'text/plain':\n return 'txt'\n case 'text/markdown':\n case 'text/x-markdown':\n return 'md'\n default:\n throw new Error(\n `Bedrock Converse: unsupported document MIME type \"${mime}\". Supported types: pdf, csv, doc, docx, xls, xlsx, html, txt, md.`,\n )\n }\n}\n\nfunction stringContent(content: string | null | Array<ContentPart>): string {\n if (content === null) return ''\n if (typeof content === 'string') return content\n return content\n .filter((p): p is TextPart => p.type === 'text')\n .map((p) => p.content)\n .join('')\n}\n\nfunction isTextPart(p: ContentPart): p is TextPart {\n return p.type === 'text'\n}\n\nfunction isImagePart(p: ContentPart): p is ImagePart {\n return p.type === 'image'\n}\n\nfunction isDocumentPart(p: ContentPart): p is DocumentPart {\n return p.type === 'document'\n}\n\nfunction isDataSource(\n source: ImagePart['source'] | DocumentPart['source'],\n): source is ContentPartDataSource {\n return source.type === 'data'\n}\n\nfunction contentPartToBlock(part: ContentPart, docIndex: number): ContentBlock {\n if (isTextPart(part)) {\n return { text: part.content }\n }\n\n if (isImagePart(part)) {\n const { source } = part\n if (!isDataSource(source)) {\n throw new Error(\n 'Bedrock Converse requires inline image bytes; URL image sources are not supported.',\n )\n }\n return {\n image: {\n format: imageFormat(source.mimeType),\n source: { bytes: base64ToBytes(source.value) },\n },\n }\n }\n\n if (isDocumentPart(part)) {\n const { source } = part\n if (!isDataSource(source)) {\n throw new Error(\n 'Bedrock Converse requires inline document bytes; URL document sources are not supported.',\n )\n }\n return {\n document: {\n format: documentFormat(source.mimeType),\n name: `document-${docIndex}`,\n source: { bytes: base64ToBytes(source.value) },\n },\n }\n }\n\n // Fail loud for unsupported part types (audio, video, etc.)\n throw new Error(\n `Bedrock Converse does not support content part type \"${String(part.type)}\".`,\n )\n}\n\nfunction messageToBlocks(\n msg: ModelMessage,\n docCounter: { value: number },\n): Array<ContentBlock> {\n const blocks: Array<ContentBlock> = []\n\n if (msg.role === 'tool') {\n if (!msg.toolCallId) {\n throw new Error(\n 'Bedrock Converse: tool message is missing toolCallId. Every tool result must reference the tool use ID it is responding to.',\n )\n }\n const textContent = stringContent(msg.content)\n const toolResult: ToolResultContentBlock = { text: textContent }\n blocks.push({\n toolResult: {\n toolUseId: msg.toolCallId,\n content: [toolResult],\n status: 'success',\n },\n })\n return blocks\n }\n\n // Map content field to text/image/document blocks\n if (typeof msg.content === 'string') {\n if (msg.content !== '') {\n blocks.push({ text: msg.content })\n }\n } else if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n const docIndex = isDocumentPart(part) ? ++docCounter.value : 0\n blocks.push(contentPartToBlock(part, docIndex))\n }\n }\n // null → no text blocks\n\n // Append toolUse blocks for assistant tool calls. Malformed or non-object\n // arguments come from a prior assistant turn the engine already accepted, so\n // they signal a real upstream problem — throw rather than silently coercing to\n // `{}` and forwarding a corrupted tool call (the adapter's catch surfaces it\n // as a RUN_ERROR).\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const call of msg.toolCalls) {\n const rawArguments = call.function.arguments || '{}'\n let parsed: unknown\n try {\n parsed = JSON.parse(rawArguments)\n } catch (error: unknown) {\n throw new Error(\n `Bedrock Converse: tool call \"${call.function.name}\" has malformed JSON arguments (${String(error)}). Raw: ${rawArguments}`,\n )\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n throw new Error(\n `Bedrock Converse: tool call \"${call.function.name}\" arguments must be a JSON object, got ${Array.isArray(parsed) ? 'array' : typeof parsed}.`,\n )\n }\n blocks.push({\n toolUse: {\n toolUseId: call.id,\n name: call.function.name,\n input: parsed as DocumentType,\n },\n })\n }\n }\n\n return blocks\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Convert TanStack AI messages + system prompts into the Converse API format.\n *\n * - System prompts are lifted into `SystemContentBlock[]`.\n * - `tool` role messages are remapped to `user` role `toolResult` blocks.\n * - Consecutive messages with the same Converse role are merged (Converse\n * requires strict user/assistant alternation).\n */\nexport function toConverseMessages(\n messages: Array<ModelMessage>,\n systemPrompts?: Array<SystemPrompt>,\n): { system: Array<SystemContentBlock>; messages: Array<Message> } {\n // Build system blocks (uses normalizeSystemPrompts for runtime validation)\n const system: Array<SystemContentBlock> = normalizeSystemPrompts(\n systemPrompts,\n ).map((p) => ({ text: p.content }))\n\n // Convert each ModelMessage to a Converse Message, merging same-role pairs\n const converseMessages: Array<Message> = []\n // Global document counter: ensures every document block across all messages\n // gets a unique name, preventing Bedrock ValidationException for duplicate names.\n const docCounter = { value: 0 }\n\n for (const msg of messages) {\n // Map TanStack roles to Converse roles\n const converseRole: 'user' | 'assistant' =\n msg.role === 'assistant' ? 'assistant' : 'user'\n\n const blocks = messageToBlocks(msg, docCounter)\n\n // Skip messages that produce no content blocks (e.g. assistant with\n // null content and no toolCalls). Pushing an empty-content message to\n // Converse triggers a ValidationException.\n if (blocks.length === 0) continue\n\n const last = converseMessages[converseMessages.length - 1]\n if (last && last.role === converseRole) {\n // Merge into the previous message's content array\n last.content = [...(last.content ?? []), ...blocks]\n } else {\n converseMessages.push({ role: converseRole, content: blocks })\n }\n }\n\n return { system, messages: converseMessages }\n}\n"],"mappings":"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"}
|
|
@@ -1,203 +1,225 @@
|
|
|
1
1
|
import { EventType } from "@tanstack/ai";
|
|
2
|
+
//#region src/converse/stream-processor.ts
|
|
3
|
+
/**
|
|
4
|
+
* Converse delivers server-side failures — throttling, request validation,
|
|
5
|
+
* mid-stream model faults, and service-unavailable — as in-band stream events
|
|
6
|
+
* rather than thrown exceptions. If they were ignored the iterator would simply
|
|
7
|
+
* end and the run would look like a clean, truncated success. Throw the
|
|
8
|
+
* underlying exception (these SDK members extend `Error`) so the adapter's
|
|
9
|
+
* `chatStream` / `structuredOutputStream` catch converts it into a `RUN_ERROR`.
|
|
10
|
+
*/
|
|
2
11
|
function throwIfConverseStreamError(ev) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
if ("validationException" in ev && ev.validationException) {
|
|
10
|
-
throw ev.validationException;
|
|
11
|
-
}
|
|
12
|
-
if ("throttlingException" in ev && ev.throttlingException) {
|
|
13
|
-
throw ev.throttlingException;
|
|
14
|
-
}
|
|
15
|
-
if ("serviceUnavailableException" in ev && ev.serviceUnavailableException) {
|
|
16
|
-
throw ev.serviceUnavailableException;
|
|
17
|
-
}
|
|
12
|
+
if ("internalServerException" in ev && ev.internalServerException) throw ev.internalServerException;
|
|
13
|
+
if ("modelStreamErrorException" in ev && ev.modelStreamErrorException) throw ev.modelStreamErrorException;
|
|
14
|
+
if ("validationException" in ev && ev.validationException) throw ev.validationException;
|
|
15
|
+
if ("throttlingException" in ev && ev.throttlingException) throw ev.throttlingException;
|
|
16
|
+
if ("serviceUnavailableException" in ev && ev.serviceUnavailableException) throw ev.serviceUnavailableException;
|
|
18
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Maps a Bedrock Converse `ConverseStreamOutput` event stream to the TanStack
|
|
20
|
+
* AG-UI `StreamChunk` lifecycle, following `openai-base`'s `processStreamChunks`
|
|
21
|
+
* lifecycle shape so the activity layer / agent loop behave identically across
|
|
22
|
+
* providers. (Reasoning surfaces only the message-level events —
|
|
23
|
+
* `REASONING_MESSAGE_START/CONTENT/END` — which is all the core accumulator
|
|
24
|
+
* consumes; the `REASONING_START`/`REASONING_END`/`STEP_*` boundary events
|
|
25
|
+
* openai-base also emits are no-ops in the engine and are not reproduced here.)
|
|
26
|
+
*
|
|
27
|
+
* Lifecycle ownership matches openai-base: this processor emits the full
|
|
28
|
+
* success-path lifecycle itself — `RUN_STARTED` lazily before the first event,
|
|
29
|
+
* `TEXT_MESSAGE_*` / `TOOL_CALL_*` / `REASONING_MESSAGE_*` for content, and a
|
|
30
|
+
* single terminal `RUN_FINISHED` once the iterator is exhausted (so the trailing
|
|
31
|
+
* `metadata` usage event is folded into the finish event regardless of arrival
|
|
32
|
+
* order). The calling adapter only owns the catch/`RUN_ERROR` path — so any
|
|
33
|
+
* in-band Converse error event (see `throwIfConverseStreamError`) is thrown to
|
|
34
|
+
* surface there rather than ending the stream as a clean truncated success.
|
|
35
|
+
*
|
|
36
|
+
* Converse streams tool-call arguments as partial-JSON string fragments inside
|
|
37
|
+
* `contentBlockDelta.delta.toolUse.input`; each fragment is emitted as a
|
|
38
|
+
* `TOOL_CALL_ARGS` `delta`, mirroring OpenAI's `function.arguments` deltas.
|
|
39
|
+
*
|
|
40
|
+
* @param stream - The Converse event stream from `ConverseStreamCommand`.
|
|
41
|
+
* @param newMessageId - Factory for fresh ids — run, thread, message, and
|
|
42
|
+
* tool-call ids (the adapter passes `() => this.generateId()`).
|
|
43
|
+
* @param lifecycle - Incoming run lifecycle ids, threaded onto the emitted
|
|
44
|
+
* `RUN_STARTED`/`RUN_FINISHED` so the chat path matches every sibling adapter
|
|
45
|
+
* (openai-base reuses `options.threadId`/`parentRunId`). Defaults preserve the
|
|
46
|
+
* previous behaviour (fresh `threadId`, no `parentRunId`).
|
|
47
|
+
*/
|
|
19
48
|
async function* processConverseStream(stream, newMessageId, lifecycle = {}) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
runId,
|
|
193
|
-
threadId,
|
|
194
|
-
timestamp: Date.now(),
|
|
195
|
-
finishReason: finishReason ?? "stop",
|
|
196
|
-
...usage && { usage }
|
|
197
|
-
};
|
|
49
|
+
const runId = newMessageId();
|
|
50
|
+
const threadId = lifecycle.threadId ?? newMessageId();
|
|
51
|
+
const { parentRunId, model } = lifecycle;
|
|
52
|
+
const messageId = newMessageId();
|
|
53
|
+
let hasEmittedRunStarted = false;
|
|
54
|
+
let accumulatedContent = "";
|
|
55
|
+
let hasEmittedTextMessageStart = false;
|
|
56
|
+
let reasoningMessageId;
|
|
57
|
+
let hasClosedReasoning = false;
|
|
58
|
+
const toolCallsByIndex = /* @__PURE__ */ new Map();
|
|
59
|
+
let usage;
|
|
60
|
+
let finishReason;
|
|
61
|
+
function* ensureRunStarted() {
|
|
62
|
+
if (hasEmittedRunStarted) return;
|
|
63
|
+
hasEmittedRunStarted = true;
|
|
64
|
+
yield {
|
|
65
|
+
type: EventType.RUN_STARTED,
|
|
66
|
+
runId,
|
|
67
|
+
threadId,
|
|
68
|
+
parentRunId,
|
|
69
|
+
...model && { model },
|
|
70
|
+
timestamp: Date.now()
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function* closeReasoning() {
|
|
74
|
+
if (reasoningMessageId && !hasClosedReasoning) {
|
|
75
|
+
hasClosedReasoning = true;
|
|
76
|
+
yield {
|
|
77
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
78
|
+
messageId: reasoningMessageId,
|
|
79
|
+
timestamp: Date.now()
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for await (const ev of stream) {
|
|
84
|
+
yield* ensureRunStarted();
|
|
85
|
+
throwIfConverseStreamError(ev);
|
|
86
|
+
if ("messageStart" in ev) continue;
|
|
87
|
+
if ("contentBlockStart" in ev) {
|
|
88
|
+
const start = ev.contentBlockStart;
|
|
89
|
+
const toolUse = start?.start?.toolUse;
|
|
90
|
+
if (start && toolUse) {
|
|
91
|
+
yield* closeReasoning();
|
|
92
|
+
const id = toolUse.toolUseId ?? newMessageId();
|
|
93
|
+
const name = toolUse.name ?? "";
|
|
94
|
+
const index = start.contentBlockIndex ?? 0;
|
|
95
|
+
toolCallsByIndex.set(index, {
|
|
96
|
+
id,
|
|
97
|
+
name,
|
|
98
|
+
started: true
|
|
99
|
+
});
|
|
100
|
+
yield {
|
|
101
|
+
type: EventType.TOOL_CALL_START,
|
|
102
|
+
toolCallId: id,
|
|
103
|
+
toolCallName: name,
|
|
104
|
+
toolName: name,
|
|
105
|
+
timestamp: Date.now(),
|
|
106
|
+
index
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if ("contentBlockDelta" in ev) {
|
|
112
|
+
const block = ev.contentBlockDelta;
|
|
113
|
+
const delta = block?.delta;
|
|
114
|
+
const index = block?.contentBlockIndex ?? 0;
|
|
115
|
+
if (delta && "toolUse" in delta && delta.toolUse?.input !== void 0) {
|
|
116
|
+
const toolCall = toolCallsByIndex.get(index);
|
|
117
|
+
if (toolCall?.started) yield {
|
|
118
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
119
|
+
toolCallId: toolCall.id,
|
|
120
|
+
timestamp: Date.now(),
|
|
121
|
+
delta: delta.toolUse.input
|
|
122
|
+
};
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (delta && "reasoningContent" in delta && delta.reasoningContent && "text" in delta.reasoningContent && delta.reasoningContent.text !== void 0) {
|
|
126
|
+
if (!reasoningMessageId) {
|
|
127
|
+
reasoningMessageId = newMessageId();
|
|
128
|
+
yield {
|
|
129
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
130
|
+
messageId: reasoningMessageId,
|
|
131
|
+
role: "reasoning",
|
|
132
|
+
timestamp: Date.now()
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
yield {
|
|
136
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
137
|
+
messageId: reasoningMessageId,
|
|
138
|
+
delta: delta.reasoningContent.text,
|
|
139
|
+
timestamp: Date.now()
|
|
140
|
+
};
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (delta && "text" in delta && delta.text !== void 0) {
|
|
144
|
+
yield* closeReasoning();
|
|
145
|
+
if (!hasEmittedTextMessageStart) {
|
|
146
|
+
hasEmittedTextMessageStart = true;
|
|
147
|
+
yield {
|
|
148
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
149
|
+
messageId,
|
|
150
|
+
role: "assistant",
|
|
151
|
+
timestamp: Date.now()
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
accumulatedContent += delta.text;
|
|
155
|
+
yield {
|
|
156
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
157
|
+
messageId,
|
|
158
|
+
delta: delta.text,
|
|
159
|
+
content: accumulatedContent,
|
|
160
|
+
timestamp: Date.now()
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if ("contentBlockStop" in ev) {
|
|
166
|
+
const stopIndex = ev.contentBlockStop?.contentBlockIndex ?? 0;
|
|
167
|
+
const toolCall = toolCallsByIndex.get(stopIndex);
|
|
168
|
+
if (toolCall?.started) {
|
|
169
|
+
yield {
|
|
170
|
+
type: EventType.TOOL_CALL_END,
|
|
171
|
+
toolCallId: toolCall.id,
|
|
172
|
+
toolCallName: toolCall.name,
|
|
173
|
+
toolName: toolCall.name,
|
|
174
|
+
timestamp: Date.now()
|
|
175
|
+
};
|
|
176
|
+
toolCallsByIndex.delete(stopIndex);
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if ("messageStop" in ev) {
|
|
181
|
+
const stopReason = ev.messageStop?.stopReason;
|
|
182
|
+
finishReason = stopReason === "tool_use" ? "tool_calls" : stopReason === "max_tokens" ? "length" : stopReason === "content_filtered" ? "content_filter" : "stop";
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if ("metadata" in ev) {
|
|
186
|
+
const u = ev.metadata?.usage;
|
|
187
|
+
if (u) usage = {
|
|
188
|
+
promptTokens: u.inputTokens ?? 0,
|
|
189
|
+
completionTokens: u.outputTokens ?? 0,
|
|
190
|
+
totalTokens: u.totalTokens ?? 0
|
|
191
|
+
};
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
yield* ensureRunStarted();
|
|
196
|
+
for (const [index, toolCall] of toolCallsByIndex) {
|
|
197
|
+
if (!toolCall.started) continue;
|
|
198
|
+
yield {
|
|
199
|
+
type: EventType.TOOL_CALL_END,
|
|
200
|
+
toolCallId: toolCall.id,
|
|
201
|
+
toolCallName: toolCall.name,
|
|
202
|
+
toolName: toolCall.name,
|
|
203
|
+
timestamp: Date.now()
|
|
204
|
+
};
|
|
205
|
+
toolCallsByIndex.delete(index);
|
|
206
|
+
}
|
|
207
|
+
if (hasEmittedTextMessageStart) yield {
|
|
208
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
209
|
+
messageId,
|
|
210
|
+
timestamp: Date.now()
|
|
211
|
+
};
|
|
212
|
+
yield* closeReasoning();
|
|
213
|
+
yield {
|
|
214
|
+
type: EventType.RUN_FINISHED,
|
|
215
|
+
runId,
|
|
216
|
+
threadId,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
finishReason: finishReason ?? "stop",
|
|
219
|
+
...usage && { usage }
|
|
220
|
+
};
|
|
198
221
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
//# sourceMappingURL=stream-processor.js.map
|
|
222
|
+
//#endregion
|
|
223
|
+
export { processConverseStream, throwIfConverseStreamError };
|
|
224
|
+
|
|
225
|
+
//# sourceMappingURL=stream-processor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-processor.js","sources":["../../../src/converse/stream-processor.ts"],"sourcesContent":["import { EventType } from '@tanstack/ai'\nimport type { RunFinishedEvent, StreamChunk } from '@tanstack/ai'\nimport type { ConverseStreamOutput } from '@aws-sdk/client-bedrock-runtime'\n\n/**\n * Converse delivers server-side failures — throttling, request validation,\n * mid-stream model faults, and service-unavailable — as in-band stream events\n * rather than thrown exceptions. If they were ignored the iterator would simply\n * end and the run would look like a clean, truncated success. Throw the\n * underlying exception (these SDK members extend `Error`) so the adapter's\n * `chatStream` / `structuredOutputStream` catch converts it into a `RUN_ERROR`.\n */\nexport function throwIfConverseStreamError(ev: ConverseStreamOutput): void {\n if ('internalServerException' in ev && ev.internalServerException) {\n throw ev.internalServerException\n }\n if ('modelStreamErrorException' in ev && ev.modelStreamErrorException) {\n throw ev.modelStreamErrorException\n }\n if ('validationException' in ev && ev.validationException) {\n throw ev.validationException\n }\n if ('throttlingException' in ev && ev.throttlingException) {\n throw ev.throttlingException\n }\n if ('serviceUnavailableException' in ev && ev.serviceUnavailableException) {\n throw ev.serviceUnavailableException\n }\n}\n\n/**\n * Maps a Bedrock Converse `ConverseStreamOutput` event stream to the TanStack\n * AG-UI `StreamChunk` lifecycle, following `openai-base`'s `processStreamChunks`\n * lifecycle shape so the activity layer / agent loop behave identically across\n * providers. (Reasoning surfaces only the message-level events —\n * `REASONING_MESSAGE_START/CONTENT/END` — which is all the core accumulator\n * consumes; the `REASONING_START`/`REASONING_END`/`STEP_*` boundary events\n * openai-base also emits are no-ops in the engine and are not reproduced here.)\n *\n * Lifecycle ownership matches openai-base: this processor emits the full\n * success-path lifecycle itself — `RUN_STARTED` lazily before the first event,\n * `TEXT_MESSAGE_*` / `TOOL_CALL_*` / `REASONING_MESSAGE_*` for content, and a\n * single terminal `RUN_FINISHED` once the iterator is exhausted (so the trailing\n * `metadata` usage event is folded into the finish event regardless of arrival\n * order). The calling adapter only owns the catch/`RUN_ERROR` path — so any\n * in-band Converse error event (see `throwIfConverseStreamError`) is thrown to\n * surface there rather than ending the stream as a clean truncated success.\n *\n * Converse streams tool-call arguments as partial-JSON string fragments inside\n * `contentBlockDelta.delta.toolUse.input`; each fragment is emitted as a\n * `TOOL_CALL_ARGS` `delta`, mirroring OpenAI's `function.arguments` deltas.\n *\n * @param stream - The Converse event stream from `ConverseStreamCommand`.\n * @param newMessageId - Factory for fresh ids — run, thread, message, and\n * tool-call ids (the adapter passes `() => this.generateId()`).\n * @param lifecycle - Incoming run lifecycle ids, threaded onto the emitted\n * `RUN_STARTED`/`RUN_FINISHED` so the chat path matches every sibling adapter\n * (openai-base reuses `options.threadId`/`parentRunId`). Defaults preserve the\n * previous behaviour (fresh `threadId`, no `parentRunId`).\n */\nexport async function* processConverseStream(\n stream: AsyncIterable<ConverseStreamOutput>,\n newMessageId: () => string,\n lifecycle: { threadId?: string; parentRunId?: string; model?: string } = {},\n): AsyncIterable<StreamChunk> {\n const runId = newMessageId()\n const threadId = lifecycle.threadId ?? newMessageId()\n const { parentRunId, model } = lifecycle\n const messageId = newMessageId()\n\n let hasEmittedRunStarted = false\n\n // Text lifecycle\n let accumulatedContent = ''\n let hasEmittedTextMessageStart = false\n\n // Reasoning lifecycle\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n\n // Tool-call lifecycle, keyed by Converse contentBlockIndex. Converse opens a\n // tool-use block with `contentBlockStart`, streams arg fragments via\n // `contentBlockDelta`, and closes it with `contentBlockStop`.\n const toolCallsByIndex = new Map<\n number,\n { id: string; name: string; started: boolean }\n >()\n\n // Usage + finish-reason are captured during iteration and folded into the\n // single terminal RUN_FINISHED, matching openai-base's deferred-finish\n // contract (usage may arrive after the finish signal).\n let usage:\n | { promptTokens: number; completionTokens: number; totalTokens: number }\n | undefined\n let finishReason: NonNullable<RunFinishedEvent['finishReason']> | undefined\n\n // Lazily emit RUN_STARTED exactly once, before the first content event.\n function* ensureRunStarted(): Generator<StreamChunk> {\n if (hasEmittedRunStarted) return\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n parentRunId,\n ...(model && { model }),\n timestamp: Date.now(),\n }\n }\n\n // Close an open reasoning message before text/tool content begins, mirroring\n // openai-base which always emits REASONING_MESSAGE_END before TEXT_MESSAGE_START.\n function* closeReasoning(): Generator<StreamChunk> {\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n timestamp: Date.now(),\n }\n }\n }\n\n for await (const ev of stream) {\n yield* ensureRunStarted()\n\n // Surface in-band server/throttle/validation errors instead of dropping them.\n throwIfConverseStreamError(ev)\n\n // messageStart carries only the role; no AG-UI event maps to it.\n if ('messageStart' in ev) continue\n\n if ('contentBlockStart' in ev) {\n const start = ev.contentBlockStart\n const toolUse = start?.start?.toolUse\n if (start && toolUse) {\n yield* closeReasoning()\n const id = toolUse.toolUseId ?? newMessageId()\n const name = toolUse.name ?? ''\n const index = start.contentBlockIndex ?? 0\n toolCallsByIndex.set(index, {\n id,\n name,\n started: true,\n })\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: id,\n toolCallName: name,\n toolName: name,\n timestamp: Date.now(),\n index,\n }\n }\n continue\n }\n\n if ('contentBlockDelta' in ev) {\n const block = ev.contentBlockDelta\n const delta = block?.delta\n const index = block?.contentBlockIndex ?? 0\n\n // Tool-call argument fragments (partial JSON).\n if (delta && 'toolUse' in delta && delta.toolUse?.input !== undefined) {\n const toolCall = toolCallsByIndex.get(index)\n if (toolCall?.started) {\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: toolCall.id,\n timestamp: Date.now(),\n delta: delta.toolUse.input,\n }\n }\n continue\n }\n\n // Reasoning content.\n if (\n delta &&\n 'reasoningContent' in delta &&\n delta.reasoningContent &&\n 'text' in delta.reasoningContent &&\n delta.reasoningContent.text !== undefined\n ) {\n if (!reasoningMessageId) {\n reasoningMessageId = newMessageId()\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning',\n timestamp: Date.now(),\n }\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: delta.reasoningContent.text,\n timestamp: Date.now(),\n }\n continue\n }\n\n // Text content.\n if (delta && 'text' in delta && delta.text !== undefined) {\n yield* closeReasoning()\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n role: 'assistant',\n timestamp: Date.now(),\n }\n }\n accumulatedContent += delta.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n delta: delta.text,\n content: accumulatedContent,\n timestamp: Date.now(),\n }\n }\n continue\n }\n\n if ('contentBlockStop' in ev) {\n const stopIndex = ev.contentBlockStop?.contentBlockIndex ?? 0\n const toolCall = toolCallsByIndex.get(stopIndex)\n if (toolCall?.started) {\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n timestamp: Date.now(),\n }\n toolCallsByIndex.delete(stopIndex)\n }\n continue\n }\n\n if ('messageStop' in ev) {\n const stopReason = ev.messageStop?.stopReason\n // Map Converse stopReason to AG-UI's narrower finishReason vocabulary.\n finishReason =\n stopReason === 'tool_use'\n ? 'tool_calls'\n : stopReason === 'max_tokens'\n ? 'length'\n : stopReason === 'content_filtered'\n ? 'content_filter'\n : 'stop'\n continue\n }\n\n if ('metadata' in ev) {\n const u = ev.metadata?.usage\n if (u) {\n usage = {\n promptTokens: u.inputTokens ?? 0,\n completionTokens: u.outputTokens ?? 0,\n totalTokens: u.totalTokens ?? 0,\n }\n }\n continue\n }\n }\n\n // Stream ended (possibly without any content) — still emit RUN_STARTED so\n // consumers always see a run lifecycle.\n yield* ensureRunStarted()\n\n // Drain any tool call that opened but never received contentBlockStop.\n for (const [index, toolCall] of toolCallsByIndex) {\n if (!toolCall.started) continue\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n timestamp: Date.now(),\n }\n toolCallsByIndex.delete(index)\n }\n\n // Close the text message lifecycle if it was opened.\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n timestamp: Date.now(),\n }\n }\n\n // Close any reasoning lifecycle that text never closed.\n yield* closeReasoning()\n\n // Single terminal RUN_FINISHED. Conditional `usage` spread keeps the wire\n // shape spec-compliant (AG-UI's `usage` is optional with no `| undefined`).\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n timestamp: Date.now(),\n finishReason: finishReason ?? 'stop',\n ...(usage && { usage }),\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stream-processor.js","names":[],"sources":["../../../src/converse/stream-processor.ts"],"sourcesContent":["import { EventType } from '@tanstack/ai'\nimport type { RunFinishedEvent, StreamChunk } from '@tanstack/ai'\nimport type { ConverseStreamOutput } from '@aws-sdk/client-bedrock-runtime'\n\n/**\n * Converse delivers server-side failures — throttling, request validation,\n * mid-stream model faults, and service-unavailable — as in-band stream events\n * rather than thrown exceptions. If they were ignored the iterator would simply\n * end and the run would look like a clean, truncated success. Throw the\n * underlying exception (these SDK members extend `Error`) so the adapter's\n * `chatStream` / `structuredOutputStream` catch converts it into a `RUN_ERROR`.\n */\nexport function throwIfConverseStreamError(ev: ConverseStreamOutput): void {\n if ('internalServerException' in ev && ev.internalServerException) {\n throw ev.internalServerException\n }\n if ('modelStreamErrorException' in ev && ev.modelStreamErrorException) {\n throw ev.modelStreamErrorException\n }\n if ('validationException' in ev && ev.validationException) {\n throw ev.validationException\n }\n if ('throttlingException' in ev && ev.throttlingException) {\n throw ev.throttlingException\n }\n if ('serviceUnavailableException' in ev && ev.serviceUnavailableException) {\n throw ev.serviceUnavailableException\n }\n}\n\n/**\n * Maps a Bedrock Converse `ConverseStreamOutput` event stream to the TanStack\n * AG-UI `StreamChunk` lifecycle, following `openai-base`'s `processStreamChunks`\n * lifecycle shape so the activity layer / agent loop behave identically across\n * providers. (Reasoning surfaces only the message-level events —\n * `REASONING_MESSAGE_START/CONTENT/END` — which is all the core accumulator\n * consumes; the `REASONING_START`/`REASONING_END`/`STEP_*` boundary events\n * openai-base also emits are no-ops in the engine and are not reproduced here.)\n *\n * Lifecycle ownership matches openai-base: this processor emits the full\n * success-path lifecycle itself — `RUN_STARTED` lazily before the first event,\n * `TEXT_MESSAGE_*` / `TOOL_CALL_*` / `REASONING_MESSAGE_*` for content, and a\n * single terminal `RUN_FINISHED` once the iterator is exhausted (so the trailing\n * `metadata` usage event is folded into the finish event regardless of arrival\n * order). The calling adapter only owns the catch/`RUN_ERROR` path — so any\n * in-band Converse error event (see `throwIfConverseStreamError`) is thrown to\n * surface there rather than ending the stream as a clean truncated success.\n *\n * Converse streams tool-call arguments as partial-JSON string fragments inside\n * `contentBlockDelta.delta.toolUse.input`; each fragment is emitted as a\n * `TOOL_CALL_ARGS` `delta`, mirroring OpenAI's `function.arguments` deltas.\n *\n * @param stream - The Converse event stream from `ConverseStreamCommand`.\n * @param newMessageId - Factory for fresh ids — run, thread, message, and\n * tool-call ids (the adapter passes `() => this.generateId()`).\n * @param lifecycle - Incoming run lifecycle ids, threaded onto the emitted\n * `RUN_STARTED`/`RUN_FINISHED` so the chat path matches every sibling adapter\n * (openai-base reuses `options.threadId`/`parentRunId`). Defaults preserve the\n * previous behaviour (fresh `threadId`, no `parentRunId`).\n */\nexport async function* processConverseStream(\n stream: AsyncIterable<ConverseStreamOutput>,\n newMessageId: () => string,\n lifecycle: { threadId?: string; parentRunId?: string; model?: string } = {},\n): AsyncIterable<StreamChunk> {\n const runId = newMessageId()\n const threadId = lifecycle.threadId ?? newMessageId()\n const { parentRunId, model } = lifecycle\n const messageId = newMessageId()\n\n let hasEmittedRunStarted = false\n\n // Text lifecycle\n let accumulatedContent = ''\n let hasEmittedTextMessageStart = false\n\n // Reasoning lifecycle\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n\n // Tool-call lifecycle, keyed by Converse contentBlockIndex. Converse opens a\n // tool-use block with `contentBlockStart`, streams arg fragments via\n // `contentBlockDelta`, and closes it with `contentBlockStop`.\n const toolCallsByIndex = new Map<\n number,\n { id: string; name: string; started: boolean }\n >()\n\n // Usage + finish-reason are captured during iteration and folded into the\n // single terminal RUN_FINISHED, matching openai-base's deferred-finish\n // contract (usage may arrive after the finish signal).\n let usage:\n | { promptTokens: number; completionTokens: number; totalTokens: number }\n | undefined\n let finishReason: NonNullable<RunFinishedEvent['finishReason']> | undefined\n\n // Lazily emit RUN_STARTED exactly once, before the first content event.\n function* ensureRunStarted(): Generator<StreamChunk> {\n if (hasEmittedRunStarted) return\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n parentRunId,\n ...(model && { model }),\n timestamp: Date.now(),\n }\n }\n\n // Close an open reasoning message before text/tool content begins, mirroring\n // openai-base which always emits REASONING_MESSAGE_END before TEXT_MESSAGE_START.\n function* closeReasoning(): Generator<StreamChunk> {\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n timestamp: Date.now(),\n }\n }\n }\n\n for await (const ev of stream) {\n yield* ensureRunStarted()\n\n // Surface in-band server/throttle/validation errors instead of dropping them.\n throwIfConverseStreamError(ev)\n\n // messageStart carries only the role; no AG-UI event maps to it.\n if ('messageStart' in ev) continue\n\n if ('contentBlockStart' in ev) {\n const start = ev.contentBlockStart\n const toolUse = start?.start?.toolUse\n if (start && toolUse) {\n yield* closeReasoning()\n const id = toolUse.toolUseId ?? newMessageId()\n const name = toolUse.name ?? ''\n const index = start.contentBlockIndex ?? 0\n toolCallsByIndex.set(index, {\n id,\n name,\n started: true,\n })\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: id,\n toolCallName: name,\n toolName: name,\n timestamp: Date.now(),\n index,\n }\n }\n continue\n }\n\n if ('contentBlockDelta' in ev) {\n const block = ev.contentBlockDelta\n const delta = block?.delta\n const index = block?.contentBlockIndex ?? 0\n\n // Tool-call argument fragments (partial JSON).\n if (delta && 'toolUse' in delta && delta.toolUse?.input !== undefined) {\n const toolCall = toolCallsByIndex.get(index)\n if (toolCall?.started) {\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: toolCall.id,\n timestamp: Date.now(),\n delta: delta.toolUse.input,\n }\n }\n continue\n }\n\n // Reasoning content.\n if (\n delta &&\n 'reasoningContent' in delta &&\n delta.reasoningContent &&\n 'text' in delta.reasoningContent &&\n delta.reasoningContent.text !== undefined\n ) {\n if (!reasoningMessageId) {\n reasoningMessageId = newMessageId()\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning',\n timestamp: Date.now(),\n }\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: delta.reasoningContent.text,\n timestamp: Date.now(),\n }\n continue\n }\n\n // Text content.\n if (delta && 'text' in delta && delta.text !== undefined) {\n yield* closeReasoning()\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n role: 'assistant',\n timestamp: Date.now(),\n }\n }\n accumulatedContent += delta.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n delta: delta.text,\n content: accumulatedContent,\n timestamp: Date.now(),\n }\n }\n continue\n }\n\n if ('contentBlockStop' in ev) {\n const stopIndex = ev.contentBlockStop?.contentBlockIndex ?? 0\n const toolCall = toolCallsByIndex.get(stopIndex)\n if (toolCall?.started) {\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n timestamp: Date.now(),\n }\n toolCallsByIndex.delete(stopIndex)\n }\n continue\n }\n\n if ('messageStop' in ev) {\n const stopReason = ev.messageStop?.stopReason\n // Map Converse stopReason to AG-UI's narrower finishReason vocabulary.\n finishReason =\n stopReason === 'tool_use'\n ? 'tool_calls'\n : stopReason === 'max_tokens'\n ? 'length'\n : stopReason === 'content_filtered'\n ? 'content_filter'\n : 'stop'\n continue\n }\n\n if ('metadata' in ev) {\n const u = ev.metadata?.usage\n if (u) {\n usage = {\n promptTokens: u.inputTokens ?? 0,\n completionTokens: u.outputTokens ?? 0,\n totalTokens: u.totalTokens ?? 0,\n }\n }\n continue\n }\n }\n\n // Stream ended (possibly without any content) — still emit RUN_STARTED so\n // consumers always see a run lifecycle.\n yield* ensureRunStarted()\n\n // Drain any tool call that opened but never received contentBlockStop.\n for (const [index, toolCall] of toolCallsByIndex) {\n if (!toolCall.started) continue\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n timestamp: Date.now(),\n }\n toolCallsByIndex.delete(index)\n }\n\n // Close the text message lifecycle if it was opened.\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n timestamp: Date.now(),\n }\n }\n\n // Close any reasoning lifecycle that text never closed.\n yield* closeReasoning()\n\n // Single terminal RUN_FINISHED. Conditional `usage` spread keeps the wire\n // shape spec-compliant (AG-UI's `usage` is optional with no `| undefined`).\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n timestamp: Date.now(),\n finishReason: finishReason ?? 'stop',\n ...(usage && { usage }),\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/converse/structured-output.ts
|
|
2
|
+
var STRUCTURED_TOOL_NAME = "structured_output";
|
|
3
|
+
/**
|
|
4
|
+
* Converse has no native json_schema response_format. Structured output is
|
|
5
|
+
* achieved by forcing a single tool whose input schema is the requested output
|
|
6
|
+
* schema; the model's tool-use `input` is the structured result.
|
|
7
|
+
*/
|
|
2
8
|
function buildStructuredToolConfig(schema) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
toolChoice: { tool: { name: STRUCTURED_TOOL_NAME } }
|
|
14
|
-
};
|
|
9
|
+
return {
|
|
10
|
+
tools: [{ toolSpec: {
|
|
11
|
+
name: STRUCTURED_TOOL_NAME,
|
|
12
|
+
description: "Return the final answer as structured JSON.",
|
|
13
|
+
inputSchema: { json: schema }
|
|
14
|
+
} }],
|
|
15
|
+
toolChoice: { tool: { name: STRUCTURED_TOOL_NAME } }
|
|
16
|
+
};
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//# sourceMappingURL=structured-output.js.map
|
|
18
|
+
//#endregion
|
|
19
|
+
export { STRUCTURED_TOOL_NAME, buildStructuredToolConfig };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=structured-output.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structured-output.js","sources":["../../../src/converse/structured-output.ts"],"sourcesContent":["import type { ToolConfiguration } from '@aws-sdk/client-bedrock-runtime'\nimport type { DocumentType } from '@smithy/types'\n\nexport const STRUCTURED_TOOL_NAME = 'structured_output'\n\n/**\n * Converse has no native json_schema response_format. Structured output is\n * achieved by forcing a single tool whose input schema is the requested output\n * schema; the model's tool-use `input` is the structured result.\n */\nexport function buildStructuredToolConfig(schema: unknown): ToolConfiguration {\n return {\n tools: [\n {\n toolSpec: {\n name: STRUCTURED_TOOL_NAME,\n description: 'Return the final answer as structured JSON.',\n inputSchema: { json: schema as DocumentType },\n },\n },\n ],\n toolChoice: { tool: { name: STRUCTURED_TOOL_NAME } },\n }\n}\n"],"
|
|
1
|
+
{"version":3,"file":"structured-output.js","names":[],"sources":["../../../src/converse/structured-output.ts"],"sourcesContent":["import type { ToolConfiguration } from '@aws-sdk/client-bedrock-runtime'\nimport type { DocumentType } from '@smithy/types'\n\nexport const STRUCTURED_TOOL_NAME = 'structured_output'\n\n/**\n * Converse has no native json_schema response_format. Structured output is\n * achieved by forcing a single tool whose input schema is the requested output\n * schema; the model's tool-use `input` is the structured result.\n */\nexport function buildStructuredToolConfig(schema: unknown): ToolConfiguration {\n return {\n tools: [\n {\n toolSpec: {\n name: STRUCTURED_TOOL_NAME,\n description: 'Return the final answer as structured JSON.',\n inputSchema: { json: schema as DocumentType },\n },\n },\n ],\n toolChoice: { tool: { name: STRUCTURED_TOOL_NAME } },\n }\n}\n"],"mappings":";AAGA,IAAa,uBAAuB;;;;;;AAOpC,SAAgB,0BAA0B,QAAoC;CAC5E,OAAO;EACL,OAAO,CACL,EACE,UAAU;GACR,MAAM;GACN,aAAa;GACb,aAAa,EAAE,MAAM,OAAuB;EAC9C,EACF,CACF;EACA,YAAY,EAAE,MAAM,EAAE,MAAM,qBAAqB,EAAE;CACrD;AACF"}
|