@tanstack/ai-bedrock 0.3.4 → 0.3.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.
@@ -1,6 +1,7 @@
1
1
  import { resolveBedrockAuth } from "../utils/auth.js";
2
2
  import { toConverseMessages } from "../converse/message-converter.js";
3
3
  import { toToolConfig } from "../converse/tool-converter.js";
4
+ import { buildConverseUsage } from "../converse/usage.js";
4
5
  import { processConverseStream, throwIfConverseStreamError } from "../converse/stream-processor.js";
5
6
  import { buildStructuredToolConfig } from "../converse/structured-output.js";
6
7
  import { EventType, convertSchemaToJsonSchema } from "@tanstack/ai";
@@ -156,11 +157,7 @@ var BedrockConverseTextAdapter = class extends BaseTextAdapter {
156
157
  return {
157
158
  data: structured,
158
159
  rawText: JSON.stringify(structured),
159
- ...usage && { usage: {
160
- promptTokens: usage.inputTokens ?? 0,
161
- completionTokens: usage.outputTokens ?? 0,
162
- totalTokens: usage.totalTokens ?? 0
163
- } }
160
+ ...usage && { usage: buildConverseUsage(usage) }
164
161
  };
165
162
  } catch (error) {
166
163
  chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {
@@ -243,11 +240,7 @@ var BedrockConverseTextAdapter = class extends BaseTextAdapter {
243
240
  }
244
241
  if ("metadata" in ev) {
245
242
  const u = ev.metadata?.usage;
246
- if (u) usage = {
247
- promptTokens: u.inputTokens ?? 0,
248
- completionTokens: u.outputTokens ?? 0,
249
- totalTokens: u.totalTokens ?? 0
250
- };
243
+ if (u) usage = buildConverseUsage(u);
251
244
  continue;
252
245
  }
253
246
  }
@@ -1 +1 @@
1
- {"version":3,"file":"converse-text.js","names":[],"sources":["../../../src/adapters/converse-text.ts"],"sourcesContent":["import { EventType, convertSchemaToJsonSchema } from '@tanstack/ai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { resolveBedrockAuth } from '../utils/auth'\nimport { toConverseMessages } from '../converse/message-converter'\nimport { toToolConfig } from '../converse/tool-converter'\nimport {\n processConverseStream,\n throwIfConverseStreamError,\n} from '../converse/stream-processor'\nimport {\n STRUCTURED_TOOL_NAME,\n buildStructuredToolConfig,\n} from '../converse/structured-output'\nimport type { ResolvedBedrockAuth } from '../utils/auth'\nimport type { ConverseToolInput } from '../converse/tool-converter'\nimport type * as BedrockRuntime from '@aws-sdk/client-bedrock-runtime'\nimport type {\n BedrockRuntimeClient,\n BedrockRuntimeClientConfig,\n ContentBlock,\n ConverseCommandInput,\n ConverseCommandOutput,\n ConverseStreamCommandInput,\n ConverseStreamOutput,\n} from '@aws-sdk/client-bedrock-runtime'\nimport type {\n JSONSchema,\n Modality,\n AdapterYieldChunk,\n TextOptions,\n Tool,\n} from '@tanstack/ai'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { BedrockClientConfig } from '../utils/client'\nimport type { BedrockMessageMetadataByModality } from '../message-types'\nimport type {\n BedrockConverseModels,\n ResolveConverseProviderOptions,\n ResolveInputModalities,\n} from '../model-meta'\n\n/** Config for the Converse adapter — same client config as the chat adapter. */\nexport interface BedrockConverseConfig extends BedrockClientConfig {}\n\n/**\n * Bedrock Converse text adapter. Wires the Converse translation modules (message\n * converter, tool converter, stream processor, structured-output forced-tool\n * builder) onto `@tanstack/ai`'s `BaseTextAdapter` and the\n * `@aws-sdk/client-bedrock-runtime` `BedrockRuntimeClient`.\n *\n * The success-path AG-UI lifecycle (`RUN_STARTED`..`RUN_FINISHED`) is owned by\n * `processConverseStream`; this adapter only owns the catch/`RUN_ERROR` path,\n * mirroring openai-base's `chatStream`.\n *\n * The actual SDK calls live behind two protected seams (`sendStream` / `send`)\n * so tests can subclass and inject canned Converse SDK shapes without a real\n * AWS request.\n */\nexport class BedrockConverseTextAdapter<\n TModel extends BedrockConverseModels,\n // Constraint mirrors the chat adapter (text.ts): the base parameterises\n // `TProviderOptions extends Record<string, any>`, and our default\n // `ResolveConverseProviderOptions<TModel>` resolves to an interface lacking an\n // implicit index signature — which `Record<string, unknown>` would reject but\n // `Record<string, any>` accepts. Confined to the generic constraint (the\n // established adapter pattern) — no value `as` cast is introduced.\n TProviderOptions extends Record<string, any> =\n ResolveConverseProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n BedrockMessageMetadataByModality\n> {\n override readonly kind = 'text' as const\n override readonly name = 'bedrock-converse' as const\n private clientPromise?: Promise<BedrockRuntimeClient>\n private readonly clientConfig: BedrockConverseConfig\n\n constructor(config: BedrockConverseConfig, model: TModel) {\n super({}, model)\n // Defer client construction and auth resolution: the AWS SDK is Node/\n // server-only, so we must not pull it into the static graph here. The\n // client (and its dynamic import) is built lazily on first SDK call.\n this.clientConfig = config\n }\n\n /**\n * Dynamically import `@aws-sdk/client-bedrock-runtime`. The specifier is held\n * in a variable (not a string literal) so bundler dep scanners (e.g. Vite/\n * esbuild optimizeDeps) cannot statically discover the AWS SDK and try to\n * pre-bundle it for the browser — it would fail on the SDK's Node-only\n * `fromTokenFile` export chain. The SDK is Node/server-only and is only\n * reached on a real request. `typeof import(...)` is a type-only reference\n * (erased at emit) so the imported members keep full typing.\n */\n protected importBedrockRuntime(): Promise<typeof BedrockRuntime> {\n const mod = '@aws-sdk/client-bedrock-runtime'\n return import(/* @vite-ignore */ mod) as Promise<typeof BedrockRuntime>\n }\n\n /**\n * Lazily construct the `BedrockRuntimeClient`. The dynamic import keeps\n * `@aws-sdk/client-bedrock-runtime` out of the static/browser graph and\n * defers `resolveBedrockAuth` until a real request is made.\n */\n protected async getClient(): Promise<BedrockRuntimeClient> {\n if (!this.clientPromise) {\n this.clientPromise = (async () => {\n const { BedrockRuntimeClient } = await this.importBedrockRuntime()\n const region = this.clientConfig.region ?? 'us-east-1'\n const resolved = resolveBedrockAuth(\n {\n apiKey: this.clientConfig.apiKey,\n region,\n auth: this.clientConfig.auth,\n },\n 'runtime',\n )\n return new BedrockRuntimeClient(\n this.buildClientConfig(resolved, region, this.clientConfig.baseURL),\n )\n })().catch((error: unknown) => {\n // Don't cache a rejected promise — clear it so a later call can retry\n // (e.g. after a transient import failure or fixed auth config).\n this.clientPromise = undefined\n throw error\n })\n }\n return this.clientPromise\n }\n\n /**\n * Map resolved auth + endpoint to a `BedrockRuntimeClientConfig`.\n *\n * Recent `@aws-sdk/client-bedrock-runtime` exposes a first-class `token`\n * config field for Bedrock API-key bearer auth. But the client's default\n * auth-scheme order is SigV4 first, then bearer — so passing `token` alone is\n * not enough: the SDK still resolves SigV4 and throws \"Could not load\n * credentials from any providers\". Pinning `authSchemePreference` to the\n * bearer scheme makes the API key actually get used. SigV4 uses the AWS\n * credential provider chain and the default scheme order.\n */\n protected buildClientConfig(\n resolved: ResolvedBedrockAuth,\n region: string,\n endpoint: string | undefined,\n ): BedrockRuntimeClientConfig {\n if (resolved.kind === 'bearer') {\n return {\n region,\n token: { token: resolved.token },\n authSchemePreference: ['httpBearerAuth'],\n ...(endpoint ? { endpoint } : {}),\n }\n }\n return {\n region: resolved.region,\n credentials: resolved.credentials,\n ...(endpoint ? { endpoint } : {}),\n }\n }\n\n // ---------------------------------------------------------------------------\n // SDK seams (overridden in tests so no real AWS call happens)\n // ---------------------------------------------------------------------------\n\n protected async sendStream(\n input: ConverseStreamCommandInput,\n ): Promise<AsyncIterable<ConverseStreamOutput>> {\n const { ConverseStreamCommand } = await this.importBedrockRuntime()\n const client = await this.getClient()\n const res = await client.send(new ConverseStreamCommand(input))\n if (!res.stream) {\n throw new Error('Bedrock Converse: empty stream response')\n }\n return res.stream\n }\n\n protected async send(\n input: ConverseCommandInput,\n ): Promise<ConverseCommandOutput> {\n const { ConverseCommand } = await this.importBedrockRuntime()\n const client = await this.getClient()\n return client.send(new ConverseCommand(input))\n }\n\n // ---------------------------------------------------------------------------\n // Public adapter surface\n // ---------------------------------------------------------------------------\n\n async *chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<AdapterYieldChunk> {\n try {\n options.logger.request(\n `activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: this.name, model: this.model },\n )\n const input = this.buildInput(options)\n const stream = await this.sendStream(input)\n yield* processConverseStream(stream, () => this.generateId(), {\n threadId: options.threadId,\n parentRunId: options.parentRunId,\n model: options.model,\n })\n } catch (error: unknown) {\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.chatStream failed`,\n )\n options.logger.errors(`${this.name}.chatStream fatal`, {\n error: errorPayload,\n source: `${this.name}.chatStream`,\n })\n // Conditional `code` spread keeps the wire shape spec-compliant under\n // `exactOptionalPropertyTypes` (AG-UI's `RunErrorEvent.code` is optional).\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n error: {\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n },\n }\n }\n }\n\n /**\n * Structured output via the forced-tool strategy. Converse has no native\n * json_schema response_format, so we force a single tool whose input schema\n * is the requested output schema and read the model's `toolUse.input` back as\n * the structured result.\n */\n async structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n try {\n chatOptions.logger.request(\n `activity=structuredOutput provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n const input: ConverseCommandInput = {\n ...this.buildInput(chatOptions),\n toolConfig: buildStructuredToolConfig(outputSchema),\n }\n const res = await this.send(input)\n const structured = extractStructuredToolInput(res)\n if (structured === undefined) {\n throw new Error(\n `${this.name}.structuredOutput: response contained no forced-tool output`,\n )\n }\n const usage = res.usage\n return {\n data: structured,\n rawText: JSON.stringify(structured),\n ...(usage && {\n usage: {\n promptTokens: usage.inputTokens ?? 0,\n completionTokens: usage.outputTokens ?? 0,\n totalTokens: usage.totalTokens ?? 0,\n },\n }),\n }\n } catch (error: unknown) {\n chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {\n error: toRunErrorPayload(error, `${this.name}.structuredOutput failed`),\n source: `${this.name}.structuredOutput`,\n })\n throw error\n }\n }\n\n /**\n * Streaming structured output. Same forced-tool strategy as\n * `structuredOutput`, but streamed: the forced tool's `toolUse.input` JSON\n * fragments are accumulated from the Converse stream and a terminal\n * `CUSTOM 'structured-output.complete'` event carries `{ object, raw }`,\n * mirroring openai-base's `structuredOutputStream` contract exactly.\n */\n async *structuredOutputStream(\n options: StructuredOutputOptions<TProviderOptions>,\n ): AsyncIterable<AdapterYieldChunk> {\n const { chatOptions, outputSchema } = options\n const runId = this.generateId()\n const threadId = chatOptions.threadId ?? this.generateId()\n const messageId = this.generateId()\n\n let hasEmittedRunStarted = false\n let hasEmittedTextMessageStart = false\n let accumulatedRaw = ''\n let finishReason: 'stop' | 'length' | 'content_filter' = 'stop'\n // Usage arrives on the trailing `metadata` event, after the finish signal,\n // so it is captured during iteration and folded into RUN_FINISHED below.\n let usage:\n | { promptTokens: number; completionTokens: number; totalTokens: number }\n | undefined\n\n try {\n chatOptions.logger.request(\n `activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n const input: ConverseStreamCommandInput = {\n ...this.buildInput(chatOptions),\n toolConfig: buildStructuredToolConfig(outputSchema),\n }\n const stream = await this.sendStream(input)\n\n // The forced tool streams its `input` as partial-JSON fragments inside\n // `contentBlockDelta.delta.toolUse.input`. We surface them as\n // TEXT_MESSAGE_CONTENT deltas (raw JSON text), matching openai-base which\n // carries the structured JSON as text deltas.\n for await (const ev of stream) {\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n // Surface in-band server/throttle/validation errors instead of\n // letting them fall through and masquerade as an empty response.\n throwIfConverseStreamError(ev)\n\n if ('contentBlockDelta' in ev) {\n const delta = ev.contentBlockDelta?.delta\n const fragment =\n delta && 'toolUse' in delta ? delta.toolUse?.input : undefined\n if (fragment !== undefined) {\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n role: 'assistant',\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n }\n accumulatedRaw += fragment\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n delta: fragment,\n content: accumulatedRaw,\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n }\n continue\n }\n\n if ('messageStop' in ev) {\n const stopReason = ev.messageStop?.stopReason\n // The forced structured-output tool produces stopReason 'tool_use' on\n // success, but that's an implementation detail — a cleanly-completed\n // structured run reports 'stop', matching openai-base's contract.\n finishReason =\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 if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n }\n\n if (accumulatedRaw.length === 0) {\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: chatOptions.model,\n timestamp: Date.now(),\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n error: {\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n },\n }\n return\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(accumulatedRaw)\n } catch {\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: chatOptions.model,\n timestamp: Date.now(),\n message: `Failed to parse structured output as JSON. Content: ${accumulatedRaw.slice(0, 200)}${accumulatedRaw.length > 200 ? '...' : ''}`,\n code: 'parse-error',\n error: {\n message: 'Failed to parse structured output as JSON',\n code: 'parse-error',\n },\n }\n return\n }\n\n yield {\n type: EventType.CUSTOM,\n name: 'structured-output.complete',\n value: {\n object: parsed,\n raw: accumulatedRaw,\n },\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n finishReason,\n ...(usage && { usage }),\n }\n } catch (error: unknown) {\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.structuredOutputStream failed`,\n )\n chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {\n error: errorPayload,\n source: `${this.name}.structuredOutputStream`,\n })\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: chatOptions.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n error: {\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n },\n }\n }\n }\n\n /**\n * Converse sends `tools` and a forced structured-output tool via two separate\n * mechanisms, never together. Declaring `false` makes the engine run the\n * agent loop without `outputSchema` and finalize via `structuredOutput` /\n * `structuredOutputStream`.\n */\n supportsCombinedToolsAndSchema(): boolean {\n return false\n }\n\n // ---------------------------------------------------------------------------\n // Request construction\n // ---------------------------------------------------------------------------\n\n /**\n * Translate `TextOptions` into a `ConverseCommandInput`. Shared by chatStream,\n * structuredOutput, and structuredOutputStream (the latter two override\n * `toolConfig` with the forced structured tool afterwards).\n */\n protected buildInput(\n options: TextOptions<TProviderOptions>,\n ): ConverseCommandInput {\n const { system, messages } = toConverseMessages(\n options.messages,\n options.systemPrompts,\n )\n\n const toolConfig = options.tools\n ? toToolConfig(convertTools(options.tools), 'auto')\n : undefined\n\n // Sampling options live on `modelOptions` (typed as the narrowed\n // `BedrockConverseProviderOptions`, which surfaces the OpenAI Chat\n // Completions field names); translate them into Converse's `inferenceConfig`,\n // which uses AWS-native camelCase keys.\n const modelOptions = options.modelOptions\n const temperature = modelOptions?.temperature\n const topP = modelOptions?.top_p\n const maxTokens = modelOptions?.max_completion_tokens\n const stop = modelOptions?.stop\n const stopSequences =\n stop == null ? undefined : Array.isArray(stop) ? stop : [stop]\n\n const inferenceConfig =\n temperature != null ||\n topP != null ||\n maxTokens != null ||\n stopSequences != null\n ? {\n ...(temperature != null && { temperature }),\n ...(topP != null && { topP }),\n ...(maxTokens != null && { maxTokens }),\n ...(stopSequences != null && { stopSequences }),\n }\n : undefined\n\n return {\n modelId: this.model,\n messages,\n ...(system.length > 0 && { system }),\n ...(toolConfig && { toolConfig }),\n ...(inferenceConfig && { inferenceConfig }),\n }\n }\n}\n\n/**\n * Convert TanStack `Tool[]` to the Converse tool-converter input shape. Reuses\n * the SAME `convertSchemaToJsonSchema` the other adapters use so the Converse\n * tool input schemas match what every other provider sends.\n */\nfunction convertTools(tools: Array<Tool>): Array<ConverseToolInput> {\n return tools.map((tool) => {\n const inputSchema: JSONSchema = convertSchemaToJsonSchema(\n tool.inputSchema,\n ) ?? { type: 'object', properties: {}, required: [] }\n return {\n name: tool.name,\n description: tool.description,\n inputSchema,\n }\n })\n}\n\n/**\n * Find the forced structured-output tool's `input` in a non-streaming Converse\n * response. SDK-boundary narrowing only — `ConverseOutput` is a tagged union\n * (`{ message }`) and a tool-use block is `{ toolUse: { input } }`.\n */\nfunction extractStructuredToolInput(\n res: ConverseCommandOutput,\n): unknown | undefined {\n const message =\n res.output && 'message' in res.output ? res.output.message : undefined\n const content: Array<ContentBlock> = message?.content ?? []\n for (const block of content) {\n if ('toolUse' in block && block.toolUse) {\n // Only accept the forced structured tool (an unnamed block is allowed,\n // since the forced tool is the only one configured). A differently-named\n // tool-use block is a hallucinated/leftover call whose arbitrary input\n // must not be returned as the validated result — leave it to the caller's\n // `throw` so the failure is accurate instead of silently wrong.\n if (\n block.toolUse.name === STRUCTURED_TOOL_NAME ||\n block.toolUse.name === undefined\n ) {\n return block.toolUse.input\n }\n }\n }\n return undefined\n}\n\n/** Converse adapter with an explicit API key (low-level; mirrors createBedrockChat). */\nexport function createBedrockConverse<TModel extends BedrockConverseModels>(\n model: TModel,\n apiKey: string,\n config?: Omit<BedrockConverseConfig, 'apiKey'>,\n): BedrockConverseTextAdapter<TModel> {\n return new BedrockConverseTextAdapter({ ...config, apiKey }, model)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"converse-text.js","names":[],"sources":["../../../src/adapters/converse-text.ts"],"sourcesContent":["import { EventType, convertSchemaToJsonSchema } from '@tanstack/ai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { resolveBedrockAuth } from '../utils/auth'\nimport { toConverseMessages } from '../converse/message-converter'\nimport { toToolConfig } from '../converse/tool-converter'\nimport {\n processConverseStream,\n throwIfConverseStreamError,\n} from '../converse/stream-processor'\nimport { buildConverseUsage } from '../converse/usage'\nimport {\n STRUCTURED_TOOL_NAME,\n buildStructuredToolConfig,\n} from '../converse/structured-output'\nimport type { ResolvedBedrockAuth } from '../utils/auth'\nimport type { ConverseToolInput } from '../converse/tool-converter'\nimport type * as BedrockRuntime from '@aws-sdk/client-bedrock-runtime'\nimport type {\n BedrockRuntimeClient,\n BedrockRuntimeClientConfig,\n ContentBlock,\n ConverseCommandInput,\n ConverseCommandOutput,\n ConverseStreamCommandInput,\n ConverseStreamOutput,\n} from '@aws-sdk/client-bedrock-runtime'\nimport type {\n JSONSchema,\n Modality,\n AdapterYieldChunk,\n TextOptions,\n TokenUsage,\n Tool,\n} from '@tanstack/ai'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { BedrockClientConfig } from '../utils/client'\nimport type { BedrockMessageMetadataByModality } from '../message-types'\nimport type {\n BedrockConverseModels,\n ResolveConverseProviderOptions,\n ResolveInputModalities,\n} from '../model-meta'\n\n/** Config for the Converse adapter — same client config as the chat adapter. */\nexport interface BedrockConverseConfig extends BedrockClientConfig {}\n\n/**\n * Bedrock Converse text adapter. Wires the Converse translation modules (message\n * converter, tool converter, stream processor, structured-output forced-tool\n * builder) onto `@tanstack/ai`'s `BaseTextAdapter` and the\n * `@aws-sdk/client-bedrock-runtime` `BedrockRuntimeClient`.\n *\n * The success-path AG-UI lifecycle (`RUN_STARTED`..`RUN_FINISHED`) is owned by\n * `processConverseStream`; this adapter only owns the catch/`RUN_ERROR` path,\n * mirroring openai-base's `chatStream`.\n *\n * The actual SDK calls live behind two protected seams (`sendStream` / `send`)\n * so tests can subclass and inject canned Converse SDK shapes without a real\n * AWS request.\n */\nexport class BedrockConverseTextAdapter<\n TModel extends BedrockConverseModels,\n // Constraint mirrors the chat adapter (text.ts): the base parameterises\n // `TProviderOptions extends Record<string, any>`, and our default\n // `ResolveConverseProviderOptions<TModel>` resolves to an interface lacking an\n // implicit index signature — which `Record<string, unknown>` would reject but\n // `Record<string, any>` accepts. Confined to the generic constraint (the\n // established adapter pattern) — no value `as` cast is introduced.\n TProviderOptions extends Record<string, any> =\n ResolveConverseProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n BedrockMessageMetadataByModality\n> {\n override readonly kind = 'text' as const\n override readonly name = 'bedrock-converse' as const\n private clientPromise?: Promise<BedrockRuntimeClient>\n private readonly clientConfig: BedrockConverseConfig\n\n constructor(config: BedrockConverseConfig, model: TModel) {\n super({}, model)\n // Defer client construction and auth resolution: the AWS SDK is Node/\n // server-only, so we must not pull it into the static graph here. The\n // client (and its dynamic import) is built lazily on first SDK call.\n this.clientConfig = config\n }\n\n /**\n * Dynamically import `@aws-sdk/client-bedrock-runtime`. The specifier is held\n * in a variable (not a string literal) so bundler dep scanners (e.g. Vite/\n * esbuild optimizeDeps) cannot statically discover the AWS SDK and try to\n * pre-bundle it for the browser — it would fail on the SDK's Node-only\n * `fromTokenFile` export chain. The SDK is Node/server-only and is only\n * reached on a real request. `typeof import(...)` is a type-only reference\n * (erased at emit) so the imported members keep full typing.\n */\n protected importBedrockRuntime(): Promise<typeof BedrockRuntime> {\n const mod = '@aws-sdk/client-bedrock-runtime'\n return import(/* @vite-ignore */ mod) as Promise<typeof BedrockRuntime>\n }\n\n /**\n * Lazily construct the `BedrockRuntimeClient`. The dynamic import keeps\n * `@aws-sdk/client-bedrock-runtime` out of the static/browser graph and\n * defers `resolveBedrockAuth` until a real request is made.\n */\n protected async getClient(): Promise<BedrockRuntimeClient> {\n if (!this.clientPromise) {\n this.clientPromise = (async () => {\n const { BedrockRuntimeClient } = await this.importBedrockRuntime()\n const region = this.clientConfig.region ?? 'us-east-1'\n const resolved = resolveBedrockAuth(\n {\n apiKey: this.clientConfig.apiKey,\n region,\n auth: this.clientConfig.auth,\n },\n 'runtime',\n )\n return new BedrockRuntimeClient(\n this.buildClientConfig(resolved, region, this.clientConfig.baseURL),\n )\n })().catch((error: unknown) => {\n // Don't cache a rejected promise — clear it so a later call can retry\n // (e.g. after a transient import failure or fixed auth config).\n this.clientPromise = undefined\n throw error\n })\n }\n return this.clientPromise\n }\n\n /**\n * Map resolved auth + endpoint to a `BedrockRuntimeClientConfig`.\n *\n * Recent `@aws-sdk/client-bedrock-runtime` exposes a first-class `token`\n * config field for Bedrock API-key bearer auth. But the client's default\n * auth-scheme order is SigV4 first, then bearer — so passing `token` alone is\n * not enough: the SDK still resolves SigV4 and throws \"Could not load\n * credentials from any providers\". Pinning `authSchemePreference` to the\n * bearer scheme makes the API key actually get used. SigV4 uses the AWS\n * credential provider chain and the default scheme order.\n */\n protected buildClientConfig(\n resolved: ResolvedBedrockAuth,\n region: string,\n endpoint: string | undefined,\n ): BedrockRuntimeClientConfig {\n if (resolved.kind === 'bearer') {\n return {\n region,\n token: { token: resolved.token },\n authSchemePreference: ['httpBearerAuth'],\n ...(endpoint ? { endpoint } : {}),\n }\n }\n return {\n region: resolved.region,\n credentials: resolved.credentials,\n ...(endpoint ? { endpoint } : {}),\n }\n }\n\n // ---------------------------------------------------------------------------\n // SDK seams (overridden in tests so no real AWS call happens)\n // ---------------------------------------------------------------------------\n\n protected async sendStream(\n input: ConverseStreamCommandInput,\n ): Promise<AsyncIterable<ConverseStreamOutput>> {\n const { ConverseStreamCommand } = await this.importBedrockRuntime()\n const client = await this.getClient()\n const res = await client.send(new ConverseStreamCommand(input))\n if (!res.stream) {\n throw new Error('Bedrock Converse: empty stream response')\n }\n return res.stream\n }\n\n protected async send(\n input: ConverseCommandInput,\n ): Promise<ConverseCommandOutput> {\n const { ConverseCommand } = await this.importBedrockRuntime()\n const client = await this.getClient()\n return client.send(new ConverseCommand(input))\n }\n\n // ---------------------------------------------------------------------------\n // Public adapter surface\n // ---------------------------------------------------------------------------\n\n async *chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<AdapterYieldChunk> {\n try {\n options.logger.request(\n `activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: this.name, model: this.model },\n )\n const input = this.buildInput(options)\n const stream = await this.sendStream(input)\n yield* processConverseStream(stream, () => this.generateId(), {\n threadId: options.threadId,\n parentRunId: options.parentRunId,\n model: options.model,\n })\n } catch (error: unknown) {\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.chatStream failed`,\n )\n options.logger.errors(`${this.name}.chatStream fatal`, {\n error: errorPayload,\n source: `${this.name}.chatStream`,\n })\n // Conditional `code` spread keeps the wire shape spec-compliant under\n // `exactOptionalPropertyTypes` (AG-UI's `RunErrorEvent.code` is optional).\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n error: {\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n },\n }\n }\n }\n\n /**\n * Structured output via the forced-tool strategy. Converse has no native\n * json_schema response_format, so we force a single tool whose input schema\n * is the requested output schema and read the model's `toolUse.input` back as\n * the structured result.\n */\n async structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n try {\n chatOptions.logger.request(\n `activity=structuredOutput provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n const input: ConverseCommandInput = {\n ...this.buildInput(chatOptions),\n toolConfig: buildStructuredToolConfig(outputSchema),\n }\n const res = await this.send(input)\n const structured = extractStructuredToolInput(res)\n if (structured === undefined) {\n throw new Error(\n `${this.name}.structuredOutput: response contained no forced-tool output`,\n )\n }\n const usage = res.usage\n return {\n data: structured,\n rawText: JSON.stringify(structured),\n ...(usage && { usage: buildConverseUsage(usage) }),\n }\n } catch (error: unknown) {\n chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {\n error: toRunErrorPayload(error, `${this.name}.structuredOutput failed`),\n source: `${this.name}.structuredOutput`,\n })\n throw error\n }\n }\n\n /**\n * Streaming structured output. Same forced-tool strategy as\n * `structuredOutput`, but streamed: the forced tool's `toolUse.input` JSON\n * fragments are accumulated from the Converse stream and a terminal\n * `CUSTOM 'structured-output.complete'` event carries `{ object, raw }`,\n * mirroring openai-base's `structuredOutputStream` contract exactly.\n */\n async *structuredOutputStream(\n options: StructuredOutputOptions<TProviderOptions>,\n ): AsyncIterable<AdapterYieldChunk> {\n const { chatOptions, outputSchema } = options\n const runId = this.generateId()\n const threadId = chatOptions.threadId ?? this.generateId()\n const messageId = this.generateId()\n\n let hasEmittedRunStarted = false\n let hasEmittedTextMessageStart = false\n let accumulatedRaw = ''\n let finishReason: 'stop' | 'length' | 'content_filter' = 'stop'\n // Usage arrives on the trailing `metadata` event, after the finish signal,\n // so it is captured during iteration and folded into RUN_FINISHED below.\n let usage: TokenUsage | undefined\n\n try {\n chatOptions.logger.request(\n `activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n const input: ConverseStreamCommandInput = {\n ...this.buildInput(chatOptions),\n toolConfig: buildStructuredToolConfig(outputSchema),\n }\n const stream = await this.sendStream(input)\n\n // The forced tool streams its `input` as partial-JSON fragments inside\n // `contentBlockDelta.delta.toolUse.input`. We surface them as\n // TEXT_MESSAGE_CONTENT deltas (raw JSON text), matching openai-base which\n // carries the structured JSON as text deltas.\n for await (const ev of stream) {\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n // Surface in-band server/throttle/validation errors instead of\n // letting them fall through and masquerade as an empty response.\n throwIfConverseStreamError(ev)\n\n if ('contentBlockDelta' in ev) {\n const delta = ev.contentBlockDelta?.delta\n const fragment =\n delta && 'toolUse' in delta ? delta.toolUse?.input : undefined\n if (fragment !== undefined) {\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n role: 'assistant',\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n }\n accumulatedRaw += fragment\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n delta: fragment,\n content: accumulatedRaw,\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n }\n continue\n }\n\n if ('messageStop' in ev) {\n const stopReason = ev.messageStop?.stopReason\n // The forced structured-output tool produces stopReason 'tool_use' on\n // success, but that's an implementation detail — a cleanly-completed\n // structured run reports 'stop', matching openai-base's contract.\n finishReason =\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 = buildConverseUsage(u)\n }\n continue\n }\n }\n\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n }\n\n if (accumulatedRaw.length === 0) {\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: chatOptions.model,\n timestamp: Date.now(),\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n error: {\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n },\n }\n return\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(accumulatedRaw)\n } catch {\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: chatOptions.model,\n timestamp: Date.now(),\n message: `Failed to parse structured output as JSON. Content: ${accumulatedRaw.slice(0, 200)}${accumulatedRaw.length > 200 ? '...' : ''}`,\n code: 'parse-error',\n error: {\n message: 'Failed to parse structured output as JSON',\n code: 'parse-error',\n },\n }\n return\n }\n\n yield {\n type: EventType.CUSTOM,\n name: 'structured-output.complete',\n value: {\n object: parsed,\n raw: accumulatedRaw,\n },\n model: chatOptions.model,\n timestamp: Date.now(),\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n finishReason,\n ...(usage && { usage }),\n }\n } catch (error: unknown) {\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.structuredOutputStream failed`,\n )\n chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {\n error: errorPayload,\n source: `${this.name}.structuredOutputStream`,\n })\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: chatOptions.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n error: {\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n },\n }\n }\n }\n\n /**\n * Converse sends `tools` and a forced structured-output tool via two separate\n * mechanisms, never together. Declaring `false` makes the engine run the\n * agent loop without `outputSchema` and finalize via `structuredOutput` /\n * `structuredOutputStream`.\n */\n supportsCombinedToolsAndSchema(): boolean {\n return false\n }\n\n // ---------------------------------------------------------------------------\n // Request construction\n // ---------------------------------------------------------------------------\n\n /**\n * Translate `TextOptions` into a `ConverseCommandInput`. Shared by chatStream,\n * structuredOutput, and structuredOutputStream (the latter two override\n * `toolConfig` with the forced structured tool afterwards).\n */\n protected buildInput(\n options: TextOptions<TProviderOptions>,\n ): ConverseCommandInput {\n const { system, messages } = toConverseMessages(\n options.messages,\n options.systemPrompts,\n )\n\n const toolConfig = options.tools\n ? toToolConfig(convertTools(options.tools), 'auto')\n : undefined\n\n // Sampling options live on `modelOptions` (typed as the narrowed\n // `BedrockConverseProviderOptions`, which surfaces the OpenAI Chat\n // Completions field names); translate them into Converse's `inferenceConfig`,\n // which uses AWS-native camelCase keys.\n const modelOptions = options.modelOptions\n const temperature = modelOptions?.temperature\n const topP = modelOptions?.top_p\n const maxTokens = modelOptions?.max_completion_tokens\n const stop = modelOptions?.stop\n const stopSequences =\n stop == null ? undefined : Array.isArray(stop) ? stop : [stop]\n\n const inferenceConfig =\n temperature != null ||\n topP != null ||\n maxTokens != null ||\n stopSequences != null\n ? {\n ...(temperature != null && { temperature }),\n ...(topP != null && { topP }),\n ...(maxTokens != null && { maxTokens }),\n ...(stopSequences != null && { stopSequences }),\n }\n : undefined\n\n return {\n modelId: this.model,\n messages,\n ...(system.length > 0 && { system }),\n ...(toolConfig && { toolConfig }),\n ...(inferenceConfig && { inferenceConfig }),\n }\n }\n}\n\n/**\n * Convert TanStack `Tool[]` to the Converse tool-converter input shape. Reuses\n * the SAME `convertSchemaToJsonSchema` the other adapters use so the Converse\n * tool input schemas match what every other provider sends.\n */\nfunction convertTools(tools: Array<Tool>): Array<ConverseToolInput> {\n return tools.map((tool) => {\n const inputSchema: JSONSchema = convertSchemaToJsonSchema(\n tool.inputSchema,\n ) ?? { type: 'object', properties: {}, required: [] }\n return {\n name: tool.name,\n description: tool.description,\n inputSchema,\n }\n })\n}\n\n/**\n * Find the forced structured-output tool's `input` in a non-streaming Converse\n * response. SDK-boundary narrowing only — `ConverseOutput` is a tagged union\n * (`{ message }`) and a tool-use block is `{ toolUse: { input } }`.\n */\nfunction extractStructuredToolInput(\n res: ConverseCommandOutput,\n): unknown | undefined {\n const message =\n res.output && 'message' in res.output ? res.output.message : undefined\n const content: Array<ContentBlock> = message?.content ?? []\n for (const block of content) {\n if ('toolUse' in block && block.toolUse) {\n // Only accept the forced structured tool (an unnamed block is allowed,\n // since the forced tool is the only one configured). A differently-named\n // tool-use block is a hallucinated/leftover call whose arbitrary input\n // must not be returned as the validated result — leave it to the caller's\n // `throw` so the failure is accurate instead of silently wrong.\n if (\n block.toolUse.name === STRUCTURED_TOOL_NAME ||\n block.toolUse.name === undefined\n ) {\n return block.toolUse.input\n }\n }\n }\n return undefined\n}\n\n/** Converse adapter with an explicit API key (low-level; mirrors createBedrockChat). */\nexport function createBedrockConverse<TModel extends BedrockConverseModels>(\n model: TModel,\n apiKey: string,\n config?: Omit<BedrockConverseConfig, 'apiKey'>,\n): BedrockConverseTextAdapter<TModel> {\n return new BedrockConverseTextAdapter({ ...config, apiKey }, model)\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { buildConverseUsage } from "./usage.js";
1
2
  import { EventType } from "@tanstack/ai";
2
3
  //#region src/converse/stream-processor.ts
3
4
  /**
@@ -184,11 +185,7 @@ async function* processConverseStream(stream, newMessageId, lifecycle = {}) {
184
185
  }
185
186
  if ("metadata" in ev) {
186
187
  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
- };
188
+ if (u) usage = buildConverseUsage(u);
192
189
  continue;
193
190
  }
194
191
  }
@@ -1 +1 @@
1
- {"version":3,"file":"stream-processor.js","names":[],"sources":["../../../src/converse/stream-processor.ts"],"sourcesContent":["import { EventType } from '@tanstack/ai'\nimport type { AdapterYieldChunk } 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<AdapterYieldChunk> {\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<AdapterYieldChunk['finishReason']> | undefined\n\n // Lazily emit RUN_STARTED exactly once, before the first content event.\n function* ensureRunStarted(): Generator<AdapterYieldChunk> {\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<AdapterYieldChunk> {\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
+ {"version":3,"file":"stream-processor.js","names":[],"sources":["../../../src/converse/stream-processor.ts"],"sourcesContent":["import { EventType } from '@tanstack/ai'\nimport { buildConverseUsage } from './usage'\nimport type { AdapterYieldChunk, TokenUsage } 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<AdapterYieldChunk> {\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: TokenUsage | undefined\n let finishReason: NonNullable<AdapterYieldChunk['finishReason']> | undefined\n\n // Lazily emit RUN_STARTED exactly once, before the first content event.\n function* ensureRunStarted(): Generator<AdapterYieldChunk> {\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<AdapterYieldChunk> {\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 = buildConverseUsage(u)\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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"}
@@ -0,0 +1,12 @@
1
+ import { TokenUsage } from '@tanstack/ai';
2
+ import { TokenUsage as ConverseTokenUsage } from '@aws-sdk/client-bedrock-runtime';
3
+ /**
4
+ * Build normalized {@link TokenUsage} from a Converse `usage` object.
5
+ *
6
+ * `inputTokens` is the uncached part of the input only. Cache reads and writes
7
+ * come as separate fields and go on `promptTokensDetails`, same as the Anthropic
8
+ * and OpenAI builders. Zero is kept, unlike those builders. Bedrock leaves the
9
+ * fields out when no checkpoint applied and sends 0 when one did (a served
10
+ * checkpoint writes 0), so absent and zero are different results.
11
+ */
12
+ export declare function buildConverseUsage(usage: ConverseTokenUsage): TokenUsage;
@@ -0,0 +1,30 @@
1
+ import { buildBaseUsage } from "@tanstack/ai";
2
+ //#region src/converse/usage.ts
3
+ /**
4
+ * Build normalized {@link TokenUsage} from a Converse `usage` object.
5
+ *
6
+ * `inputTokens` is the uncached part of the input only. Cache reads and writes
7
+ * come as separate fields and go on `promptTokensDetails`, same as the Anthropic
8
+ * and OpenAI builders. Zero is kept, unlike those builders. Bedrock leaves the
9
+ * fields out when no checkpoint applied and sends 0 when one did (a served
10
+ * checkpoint writes 0), so absent and zero are different results.
11
+ */
12
+ function buildConverseUsage(usage) {
13
+ const result = buildBaseUsage({
14
+ promptTokens: usage.inputTokens ?? 0,
15
+ completionTokens: usage.outputTokens ?? 0,
16
+ totalTokens: usage.totalTokens ?? 0
17
+ });
18
+ const cachedTokens = usage.cacheReadInputTokens;
19
+ const cacheWriteTokens = usage.cacheWriteInputTokens;
20
+ const promptTokensDetails = {
21
+ ...cachedTokens !== void 0 ? { cachedTokens } : {},
22
+ ...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
23
+ };
24
+ if (Object.keys(promptTokensDetails).length > 0) result.promptTokensDetails = promptTokensDetails;
25
+ return result;
26
+ }
27
+ //#endregion
28
+ export { buildConverseUsage };
29
+
30
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","names":[],"sources":["../../../src/converse/usage.ts"],"sourcesContent":["import { buildBaseUsage } from '@tanstack/ai'\nimport type { TokenUsage } from '@tanstack/ai'\nimport type { TokenUsage as ConverseTokenUsage } from '@aws-sdk/client-bedrock-runtime'\n\n/**\n * Build normalized {@link TokenUsage} from a Converse `usage` object.\n *\n * `inputTokens` is the uncached part of the input only. Cache reads and writes\n * come as separate fields and go on `promptTokensDetails`, same as the Anthropic\n * and OpenAI builders. Zero is kept, unlike those builders. Bedrock leaves the\n * fields out when no checkpoint applied and sends 0 when one did (a served\n * checkpoint writes 0), so absent and zero are different results.\n */\nexport function buildConverseUsage(usage: ConverseTokenUsage): TokenUsage {\n const result = buildBaseUsage({\n promptTokens: usage.inputTokens ?? 0,\n completionTokens: usage.outputTokens ?? 0,\n totalTokens: usage.totalTokens ?? 0,\n })\n\n const cachedTokens = usage.cacheReadInputTokens\n const cacheWriteTokens = usage.cacheWriteInputTokens\n const promptTokensDetails = {\n ...(cachedTokens !== undefined ? { cachedTokens } : {}),\n ...(cacheWriteTokens !== undefined ? { cacheWriteTokens } : {}),\n }\n if (Object.keys(promptTokensDetails).length > 0) {\n result.promptTokensDetails = promptTokensDetails\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,mBAAmB,OAAuC;CACxE,MAAM,SAAS,eAAe;EAC5B,cAAc,MAAM,eAAe;EACnC,kBAAkB,MAAM,gBAAgB;EACxC,aAAa,MAAM,eAAe;CACpC,CAAC;CAED,MAAM,eAAe,MAAM;CAC3B,MAAM,mBAAmB,MAAM;CAC/B,MAAM,sBAAsB;EAC1B,GAAI,iBAAiB,KAAA,IAAY,EAAE,aAAa,IAAI,CAAC;EACrD,GAAI,qBAAqB,KAAA,IAAY,EAAE,iBAAiB,IAAI,CAAC;CAC/D;CACA,IAAI,OAAO,KAAK,mBAAmB,CAAC,CAAC,SAAS,GAC5C,OAAO,sBAAsB;CAG/B,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-bedrock",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "description": "Amazon Bedrock adapter for TanStack AI — OpenAI-compatible chat, responses, tools, and reasoning.",
6
6
  "author": "",
@@ -43,7 +43,7 @@
43
43
  "vite": "^8.2.1"
44
44
  },
45
45
  "peerDependencies": {
46
- "@tanstack/ai": "^0.52.1"
46
+ "@tanstack/ai": "^0.52.2"
47
47
  },
48
48
  "dependencies": {
49
49
  "@aws-crypto/sha256-js": "^5.2.0",
@@ -8,6 +8,7 @@ import {
8
8
  processConverseStream,
9
9
  throwIfConverseStreamError,
10
10
  } from '../converse/stream-processor'
11
+ import { buildConverseUsage } from '../converse/usage'
11
12
  import {
12
13
  STRUCTURED_TOOL_NAME,
13
14
  buildStructuredToolConfig,
@@ -29,6 +30,7 @@ import type {
29
30
  Modality,
30
31
  AdapterYieldChunk,
31
32
  TextOptions,
33
+ TokenUsage,
32
34
  Tool,
33
35
  } from '@tanstack/ai'
34
36
  import type {
@@ -265,13 +267,7 @@ export class BedrockConverseTextAdapter<
265
267
  return {
266
268
  data: structured,
267
269
  rawText: JSON.stringify(structured),
268
- ...(usage && {
269
- usage: {
270
- promptTokens: usage.inputTokens ?? 0,
271
- completionTokens: usage.outputTokens ?? 0,
272
- totalTokens: usage.totalTokens ?? 0,
273
- },
274
- }),
270
+ ...(usage && { usage: buildConverseUsage(usage) }),
275
271
  }
276
272
  } catch (error: unknown) {
277
273
  chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {
@@ -303,9 +299,7 @@ export class BedrockConverseTextAdapter<
303
299
  let finishReason: 'stop' | 'length' | 'content_filter' = 'stop'
304
300
  // Usage arrives on the trailing `metadata` event, after the finish signal,
305
301
  // so it is captured during iteration and folded into RUN_FINISHED below.
306
- let usage:
307
- | { promptTokens: number; completionTokens: number; totalTokens: number }
308
- | undefined
302
+ let usage: TokenUsage | undefined
309
303
 
310
304
  try {
311
305
  chatOptions.logger.request(
@@ -384,11 +378,7 @@ export class BedrockConverseTextAdapter<
384
378
  if ('metadata' in ev) {
385
379
  const u = ev.metadata?.usage
386
380
  if (u) {
387
- usage = {
388
- promptTokens: u.inputTokens ?? 0,
389
- completionTokens: u.outputTokens ?? 0,
390
- totalTokens: u.totalTokens ?? 0,
391
- }
381
+ usage = buildConverseUsage(u)
392
382
  }
393
383
  continue
394
384
  }
@@ -1,5 +1,6 @@
1
1
  import { EventType } from '@tanstack/ai'
2
- import type { AdapterYieldChunk } from '@tanstack/ai'
2
+ import { buildConverseUsage } from './usage'
3
+ import type { AdapterYieldChunk, TokenUsage } from '@tanstack/ai'
3
4
  import type { ConverseStreamOutput } from '@aws-sdk/client-bedrock-runtime'
4
5
 
5
6
  /**
@@ -89,9 +90,7 @@ export async function* processConverseStream(
89
90
  // Usage + finish-reason are captured during iteration and folded into the
90
91
  // single terminal RUN_FINISHED, matching openai-base's deferred-finish
91
92
  // contract (usage may arrive after the finish signal).
92
- let usage:
93
- | { promptTokens: number; completionTokens: number; totalTokens: number }
94
- | undefined
93
+ let usage: TokenUsage | undefined
95
94
  let finishReason: NonNullable<AdapterYieldChunk['finishReason']> | undefined
96
95
 
97
96
  // Lazily emit RUN_STARTED exactly once, before the first content event.
@@ -257,11 +256,7 @@ export async function* processConverseStream(
257
256
  if ('metadata' in ev) {
258
257
  const u = ev.metadata?.usage
259
258
  if (u) {
260
- usage = {
261
- promptTokens: u.inputTokens ?? 0,
262
- completionTokens: u.outputTokens ?? 0,
263
- totalTokens: u.totalTokens ?? 0,
264
- }
259
+ usage = buildConverseUsage(u)
265
260
  }
266
261
  continue
267
262
  }
@@ -0,0 +1,32 @@
1
+ import { buildBaseUsage } from '@tanstack/ai'
2
+ import type { TokenUsage } from '@tanstack/ai'
3
+ import type { TokenUsage as ConverseTokenUsage } from '@aws-sdk/client-bedrock-runtime'
4
+
5
+ /**
6
+ * Build normalized {@link TokenUsage} from a Converse `usage` object.
7
+ *
8
+ * `inputTokens` is the uncached part of the input only. Cache reads and writes
9
+ * come as separate fields and go on `promptTokensDetails`, same as the Anthropic
10
+ * and OpenAI builders. Zero is kept, unlike those builders. Bedrock leaves the
11
+ * fields out when no checkpoint applied and sends 0 when one did (a served
12
+ * checkpoint writes 0), so absent and zero are different results.
13
+ */
14
+ export function buildConverseUsage(usage: ConverseTokenUsage): TokenUsage {
15
+ const result = buildBaseUsage({
16
+ promptTokens: usage.inputTokens ?? 0,
17
+ completionTokens: usage.outputTokens ?? 0,
18
+ totalTokens: usage.totalTokens ?? 0,
19
+ })
20
+
21
+ const cachedTokens = usage.cacheReadInputTokens
22
+ const cacheWriteTokens = usage.cacheWriteInputTokens
23
+ const promptTokensDetails = {
24
+ ...(cachedTokens !== undefined ? { cachedTokens } : {}),
25
+ ...(cacheWriteTokens !== undefined ? { cacheWriteTokens } : {}),
26
+ }
27
+ if (Object.keys(promptTokensDetails).length > 0) {
28
+ result.promptTokensDetails = promptTokensDetails
29
+ }
30
+
31
+ return result
32
+ }