@tanstack/ai-bedrock 0.3.13 → 0.3.15

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,6 @@
1
1
  import { resolveBedrockAuth } from "../utils/auth.js";
2
2
  import { BEDROCK_EMBEDDING_MODELS } from "../model-meta.js";
3
- import { requireTextOnlyEmbeddingInput, resolveEmbeddingInput } from "@tanstack/ai";
3
+ import { isFileSource, requireTextOnlyEmbeddingInput, resolveEmbeddingInput, unsupportedFileSourceError } from "@tanstack/ai";
4
4
  import { BaseEmbeddingAdapter } from "@tanstack/ai/adapters";
5
5
  import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
6
6
  //#region src/adapters/embedding.ts
@@ -244,6 +244,7 @@ function readCohereEmbeddingBody(raw, context) {
244
244
  */
245
245
  function toTitanInputImage(image, model) {
246
246
  const source = image.source;
247
+ if (isFileSource(source)) throw unsupportedFileSourceError("bedrock");
247
248
  if (source.type === "data") return source.value;
248
249
  if (source.value.startsWith("data:")) {
249
250
  const comma = source.value.indexOf(",");
@@ -1 +1 @@
1
- {"version":3,"file":"embedding.js","names":[],"sources":["../../../src/adapters/embedding.ts"],"sourcesContent":["import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport {\n requireTextOnlyEmbeddingInput,\n resolveEmbeddingInput,\n} from '@tanstack/ai'\nimport { resolveBedrockAuth } from '../utils/auth'\nimport { BEDROCK_EMBEDDING_MODELS } from '../model-meta'\nimport type * as BedrockRuntime from '@aws-sdk/client-bedrock-runtime'\nimport type {\n BedrockRuntimeClient,\n BedrockRuntimeClientConfig,\n} from '@aws-sdk/client-bedrock-runtime'\nimport type {\n EmbeddingOptions,\n EmbeddingResult,\n ImagePart,\n TokenUsage,\n} from '@tanstack/ai'\nimport type { ResolvedBedrockAuth } from '../utils/auth'\nimport type { BedrockClientConfig } from '../utils/client'\nimport type {\n BedrockEmbeddingModel,\n BedrockEmbeddingModelInputModalitiesByName,\n BedrockEmbeddingModelProviderOptionsByName,\n ResolveEmbeddingProviderOptions,\n} from '../model-meta'\n\n/**\n * Config for the Bedrock embedding adapter — the same auth surface as the\n * other Bedrock adapters (apiKey → env → SigV4 via `resolveBedrockAuth`),\n * minus the OpenAI-compat client options that don't apply to `InvokeModel`.\n */\nexport interface BedrockEmbeddingConfig extends Pick<\n BedrockClientConfig,\n 'apiKey' | 'region' | 'auth' | 'baseURL'\n> {}\n\n/** InvokeModel calls issued concurrently during a per-item fan-out. */\nconst MAX_CONCURRENT_INVOCATIONS = 5\n\n/** Valid `dimensions` for `amazon.titan-embed-text-v2:0`. */\nconst TITAN_TEXT_DIMENSIONS: ReadonlyArray<number> = [256, 512, 1024]\n\n/** Valid `dimensions` (outputEmbeddingLength) for `amazon.titan-embed-image-v1`. */\nconst TITAN_IMAGE_DIMENSIONS: ReadonlyArray<number> = [256, 384, 1024]\nconst TITAN_IMAGE_DEFAULT_DIMENSIONS = 1024\n\n/** Cohere embed accepts at most 96 texts per InvokeModel call. */\nconst COHERE_MAX_BATCH_SIZE = 96\n\n/**\n * Bedrock Embedding Adapter\n *\n * Tree-shakeable adapter for embeddings served through Bedrock's native\n * `InvokeModel` API (embedding models have no Converse surface). Each model\n * family has its own JSON body dialect:\n *\n * - `amazon.titan-embed-text-v2:0` — text-only, ONE text per call; the batch\n * is fanned out with a small concurrency cap and per-call\n * `inputTextTokenCount`s are summed into usage.\n * - `amazon.titan-embed-image-v1` — MULTIMODAL: text, image, or a fused\n * text+image item embedded into a single vector; one item per call.\n * - `cohere.embed-english-v3` / `cohere.embed-multilingual-v3` — text-only,\n * batched natively (chunked at 96 texts per call). `inputType` is required.\n *\n * The SDK call lives behind a protected `invokeModel` seam so tests can\n * subclass and inject canned response bodies without a real AWS request, and\n * the AWS SDK itself is imported lazily (it's Node/server-only).\n */\nexport class BedrockEmbeddingAdapter<\n TModel extends BedrockEmbeddingModel,\n // Same rationale as the text adapters: the base parameterises\n // `TProviderOptions extends object`, and the per-model options interfaces\n // lack implicit index signatures — `Record<string, any>` (not `unknown`)\n // accepts them. Confined to the generic constraint; no value cast.\n TProviderOptions extends Record<string, any> =\n ResolveEmbeddingProviderOptions<TModel>,\n> extends BaseEmbeddingAdapter<\n TModel,\n TProviderOptions,\n BedrockEmbeddingModelProviderOptionsByName,\n BedrockEmbeddingModelInputModalitiesByName\n> {\n readonly name = 'bedrock' as const\n private clientPromise?: Promise<BedrockRuntimeClient>\n private readonly clientConfig: BedrockEmbeddingConfig\n\n constructor(config: BedrockEmbeddingConfig, 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\n * held in a variable (not a string literal) so bundler dep scanners cannot\n * statically discover the AWS SDK and try to pre-bundle it for the browser.\n * Same pattern as the Converse text adapter.\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`, deferring\n * `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`. Bearer\n * auth needs `authSchemePreference` pinned or the SDK still tries SigV4\n * first — same reasoning as the Converse text adapter.\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 seam (overridden in tests so no real AWS call happens)\n // ---------------------------------------------------------------------------\n\n /** Send one InvokeModel call and parse its JSON response body. */\n protected async invokeModel(\n modelId: string,\n body: Record<string, unknown>,\n ): Promise<unknown> {\n const { InvokeModelCommand } = await this.importBedrockRuntime()\n const client = await this.getClient()\n const response = await client.send(\n new InvokeModelCommand({\n modelId,\n contentType: 'application/json',\n accept: 'application/json',\n body: JSON.stringify(body),\n }),\n )\n return JSON.parse(new TextDecoder().decode(response.body))\n }\n\n // ---------------------------------------------------------------------------\n // Public adapter surface\n // ---------------------------------------------------------------------------\n\n async createEmbeddings(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, logger } = options\n try {\n logger.request(\n `activity=embed provider=${this.name} model=${model} inputs=${options.input.length}`,\n { provider: this.name, model },\n )\n switch (model) {\n case 'amazon.titan-embed-text-v2:0':\n return await this.embedTitanText(options)\n case 'amazon.titan-embed-image-v1':\n return await this.embedTitanImage(options)\n case 'cohere.embed-english-v3':\n case 'cohere.embed-multilingual-v3':\n return await this.embedCohere(options)\n default:\n throw new Error(\n `Unknown Bedrock embedding model \"${model}\". Supported models: ` +\n `${BEDROCK_EMBEDDING_MODELS.join(', ')}.`,\n )\n }\n } catch (error: unknown) {\n logger.errors(`${this.name}.createEmbeddings fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),\n source: `${this.name}.createEmbeddings`,\n })\n throw error\n }\n }\n\n // ---------------------------------------------------------------------------\n // Per-model request mapping\n // ---------------------------------------------------------------------------\n\n /**\n * `amazon.titan-embed-text-v2:0` — one text per InvokeModel call, fanned\n * out with a concurrency cap; result order matches input order and per-call\n * `inputTextTokenCount`s are summed into usage.\n */\n private async embedTitanText(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, dimensions } = options\n if (\n dimensions !== undefined &&\n !TITAN_TEXT_DIMENSIONS.includes(dimensions)\n ) {\n throw new Error(\n `${model} supports dimensions 256, 512, or 1024; got ${dimensions}`,\n )\n }\n const normalize: boolean | undefined = options.modelOptions?.normalize\n const texts = requireTextOnlyEmbeddingInput(options.input, this.name, model)\n\n const responses = await mapWithConcurrency(\n texts,\n MAX_CONCURRENT_INVOCATIONS,\n async (text) => {\n // Built incrementally: exactOptionalPropertyTypes is on, and Titan\n // rejects explicit nulls/undefined for absent optional fields.\n const body: Record<string, unknown> = { inputText: text }\n if (dimensions !== undefined) body.dimensions = dimensions\n if (normalize !== undefined) body.normalize = normalize\n return readTitanEmbeddingBody(\n await this.invokeModel(model, body),\n `${this.name} ${model}`,\n )\n },\n )\n\n return this.toTitanResult(model, responses)\n }\n\n /**\n * `amazon.titan-embed-image-v1` (Titan Multimodal) — one item per\n * InvokeModel call. An item may carry text, an image, or both (a fused\n * item embedded into a single vector). Titan accepts at most one image per\n * request and never fetches remote URLs.\n */\n private async embedTitanImage(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, dimensions } = options\n const outputEmbeddingLength = dimensions ?? TITAN_IMAGE_DEFAULT_DIMENSIONS\n if (!TITAN_IMAGE_DIMENSIONS.includes(outputEmbeddingLength)) {\n throw new Error(\n `${model} supports dimensions 256, 384, or 1024; got ${outputEmbeddingLength}`,\n )\n }\n const items = resolveEmbeddingInput(options.input)\n\n const responses = await mapWithConcurrency(\n items,\n MAX_CONCURRENT_INVOCATIONS,\n async (item, index) => {\n if (item.images.length > 1) {\n throw new Error(\n `${model} accepts at most one image per input item; input item ` +\n `at index ${index} contains ${item.images.length} images. ` +\n `Pass them as separate input items (one vector each).`,\n )\n }\n const body: Record<string, unknown> = {\n embeddingConfig: { outputEmbeddingLength },\n }\n if (item.texts.length > 0) body.inputText = item.texts.join('\\n')\n const image = item.images[0]\n if (image) body.inputImage = toTitanInputImage(image, model)\n return readTitanEmbeddingBody(\n await this.invokeModel(model, body),\n `${this.name} ${model}`,\n )\n },\n )\n\n return this.toTitanResult(model, responses)\n }\n\n /**\n * `cohere.embed-*-v3` — natively batched (chunked at 96 texts per call,\n * order preserved across chunks). `inputType` is required by the Cohere\n * API; output dimensionality is fixed, so `dimensions` is rejected.\n */\n private async embedCohere(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, dimensions } = options\n if (dimensions !== undefined) {\n throw new Error(\n `${model} does not support the dimensions option; its output size is fixed`,\n )\n }\n const inputType: string | undefined = options.modelOptions?.inputType\n if (inputType === undefined) {\n throw new Error(\n `${model} requires modelOptions.inputType ('search_document' | ` +\n `'search_query' | 'classification' | 'clustering')`,\n )\n }\n const truncate: string | undefined = options.modelOptions?.truncate\n const texts = requireTextOnlyEmbeddingInput(options.input, this.name, model)\n const batches = chunk(texts, COHERE_MAX_BATCH_SIZE)\n\n const responses = await mapWithConcurrency(\n batches,\n MAX_CONCURRENT_INVOCATIONS,\n async (batch) => {\n const body: Record<string, unknown> = {\n texts: batch,\n input_type: inputType,\n }\n if (truncate !== undefined) body.truncate = truncate\n return readCohereEmbeddingBody(\n await this.invokeModel(model, body),\n `${this.name} ${model}`,\n )\n },\n )\n\n return {\n id: this.generateId(),\n model,\n embeddings: responses.flat().map((vector, index) => ({ vector, index })),\n }\n }\n\n /** Assemble an EmbeddingResult from per-item Titan responses. */\n private toTitanResult(\n model: string,\n responses: Array<TitanEmbeddingBody>,\n ): EmbeddingResult {\n let promptTokens = 0\n const embeddings = responses.map((response, index) => {\n promptTokens += response.inputTextTokenCount\n return { vector: response.embedding, index }\n })\n const usage: TokenUsage = {\n promptTokens,\n completionTokens: 0,\n totalTokens: promptTokens,\n }\n return { id: this.generateId(), model, embeddings, usage }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Response-body narrowing (SDK JSON boundary)\n// ---------------------------------------------------------------------------\n\ninterface TitanEmbeddingBody {\n embedding: Array<number>\n /** 0 when the response omits it (e.g. image-only Titan Multimodal calls). */\n inputTextTokenCount: number\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n/** Narrow a Titan InvokeModel JSON body: `{ embedding, inputTextTokenCount? }`. */\nfunction readTitanEmbeddingBody(\n raw: unknown,\n context: string,\n): TitanEmbeddingBody {\n const embedding =\n isRecord(raw) && Array.isArray(raw.embedding) ? raw.embedding : undefined\n if (!embedding) {\n throw new Error(\n `${context}: response body is missing the \"embedding\" array`,\n )\n }\n const inputTextTokenCount =\n isRecord(raw) && typeof raw.inputTextTokenCount === 'number'\n ? raw.inputTextTokenCount\n : 0\n return { embedding, inputTextTokenCount }\n}\n\n/** Narrow a Cohere InvokeModel JSON body: `{ embeddings: number[][] }` (float). */\nfunction readCohereEmbeddingBody(\n raw: unknown,\n context: string,\n): Array<Array<number>> {\n const embeddings =\n isRecord(raw) && Array.isArray(raw.embeddings) ? raw.embeddings : undefined\n if (!embeddings) {\n throw new Error(\n `${context}: response body is missing the \"embeddings\" array`,\n )\n }\n return embeddings\n}\n\n// ---------------------------------------------------------------------------\n// Input mapping helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map an ImagePart to Titan's `inputImage` (RAW base64, no data: prefix).\n * Accepts `data` sources as-is and `url` sources ONLY when the value is a\n * `data:` URI; Titan cannot fetch remote http(s) URLs.\n */\nfunction toTitanInputImage(image: ImagePart, model: string): string {\n const source = image.source\n if (source.type === 'data') {\n return source.value\n }\n if (source.value.startsWith('data:')) {\n const comma = source.value.indexOf(',')\n if (comma !== -1) {\n return source.value.slice(comma + 1)\n }\n }\n throw new Error(\n `Bedrock Titan does not fetch remote image URLs; pass base64 data ` +\n `(a { type: 'data' } source or a data: URI) for ${model}.`,\n )\n}\n\n/** Split into runs of at most `size`, preserving order. */\nfunction chunk<T>(items: Array<T>, size: number): Array<Array<T>> {\n const chunks: Array<Array<T>> = []\n for (let i = 0; i < items.length; i += size) {\n chunks.push(items.slice(i, i + size))\n }\n return chunks\n}\n\n/**\n * Map `fn` over `items` with at most `limit` calls in flight, returning\n * results in input order (result[i] corresponds to items[i] regardless of\n * completion order). Rejects with the first error.\n */\nasync function mapWithConcurrency<T, TResult>(\n items: ReadonlyArray<T>,\n limit: number,\n fn: (item: T, index: number) => Promise<TResult>,\n): Promise<Array<TResult>> {\n const results = new Array<TResult>(items.length)\n let next = 0\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const index = next++\n const item = items[index]\n if (item === undefined) continue // unreachable: index < length\n results[index] = await fn(item, index)\n }\n }\n const workerCount = Math.max(1, Math.min(limit, items.length))\n await Promise.all(Array.from({ length: workerCount }, () => worker()))\n return results\n}\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a Bedrock embedding adapter with an explicit API key (bearer).\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'amazon.titan-embed-text-v2:0')\n * @param apiKey - Your Bedrock API key\n * @param config - Optional additional configuration (region, baseURL, ...)\n * @returns Configured Bedrock embedding adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createBedrockEmbedding(\n * 'amazon.titan-embed-text-v2:0',\n * 'bedrock-api-key',\n * );\n *\n * const result = await embed({\n * adapter,\n * input: 'a red guitar',\n * });\n * ```\n */\nexport function createBedrockEmbedding<TModel extends BedrockEmbeddingModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<BedrockEmbeddingConfig, 'apiKey'>,\n): BedrockEmbeddingAdapter<TModel> {\n // Explicit apiKey is authoritative — spread config first so it can't override.\n return new BedrockEmbeddingAdapter({ ...config, apiKey }, model)\n}\n\n/**\n * Creates a Bedrock embedding adapter using the ambient auth cascade:\n * `config.apiKey` → `BEDROCK_API_KEY` → `AWS_BEARER_TOKEN_BEDROCK` → SigV4\n * (AWS credential provider chain). Auth resolves lazily on the first\n * request, so `auth: 'sigv4'` never requires an API key.\n *\n * @param model - The model name (e.g., 'cohere.embed-english-v3')\n * @param config - Optional configuration (region, auth, baseURL, ...)\n * @returns Configured Bedrock embedding adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = bedrockEmbedding('cohere.embed-english-v3');\n *\n * const result = await embed({\n * adapter,\n * input: ['a red guitar', 'a blue drum kit'],\n * modelOptions: { inputType: 'search_document' },\n * });\n *\n * console.log(result.embeddings[0].vector)\n * ```\n */\nexport function bedrockEmbedding<TModel extends BedrockEmbeddingModel>(\n model: TModel,\n config?: BedrockEmbeddingConfig,\n): BedrockEmbeddingAdapter<TModel> {\n return new BedrockEmbeddingAdapter(config ?? {}, model)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"embedding.js","names":[],"sources":["../../../src/adapters/embedding.ts"],"sourcesContent":["import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport {\n isFileSource,\n requireTextOnlyEmbeddingInput,\n resolveEmbeddingInput,\n unsupportedFileSourceError,\n} from '@tanstack/ai'\nimport { resolveBedrockAuth } from '../utils/auth'\nimport { BEDROCK_EMBEDDING_MODELS } from '../model-meta'\nimport type * as BedrockRuntime from '@aws-sdk/client-bedrock-runtime'\nimport type {\n BedrockRuntimeClient,\n BedrockRuntimeClientConfig,\n} from '@aws-sdk/client-bedrock-runtime'\nimport type {\n EmbeddingOptions,\n EmbeddingResult,\n ImagePart,\n TokenUsage,\n} from '@tanstack/ai'\nimport type { ResolvedBedrockAuth } from '../utils/auth'\nimport type { BedrockClientConfig } from '../utils/client'\nimport type {\n BedrockEmbeddingModel,\n BedrockEmbeddingModelInputModalitiesByName,\n BedrockEmbeddingModelProviderOptionsByName,\n ResolveEmbeddingProviderOptions,\n} from '../model-meta'\n\n/**\n * Config for the Bedrock embedding adapter — the same auth surface as the\n * other Bedrock adapters (apiKey → env → SigV4 via `resolveBedrockAuth`),\n * minus the OpenAI-compat client options that don't apply to `InvokeModel`.\n */\nexport interface BedrockEmbeddingConfig extends Pick<\n BedrockClientConfig,\n 'apiKey' | 'region' | 'auth' | 'baseURL'\n> {}\n\n/** InvokeModel calls issued concurrently during a per-item fan-out. */\nconst MAX_CONCURRENT_INVOCATIONS = 5\n\n/** Valid `dimensions` for `amazon.titan-embed-text-v2:0`. */\nconst TITAN_TEXT_DIMENSIONS: ReadonlyArray<number> = [256, 512, 1024]\n\n/** Valid `dimensions` (outputEmbeddingLength) for `amazon.titan-embed-image-v1`. */\nconst TITAN_IMAGE_DIMENSIONS: ReadonlyArray<number> = [256, 384, 1024]\nconst TITAN_IMAGE_DEFAULT_DIMENSIONS = 1024\n\n/** Cohere embed accepts at most 96 texts per InvokeModel call. */\nconst COHERE_MAX_BATCH_SIZE = 96\n\n/**\n * Bedrock Embedding Adapter\n *\n * Tree-shakeable adapter for embeddings served through Bedrock's native\n * `InvokeModel` API (embedding models have no Converse surface). Each model\n * family has its own JSON body dialect:\n *\n * - `amazon.titan-embed-text-v2:0` — text-only, ONE text per call; the batch\n * is fanned out with a small concurrency cap and per-call\n * `inputTextTokenCount`s are summed into usage.\n * - `amazon.titan-embed-image-v1` — MULTIMODAL: text, image, or a fused\n * text+image item embedded into a single vector; one item per call.\n * - `cohere.embed-english-v3` / `cohere.embed-multilingual-v3` — text-only,\n * batched natively (chunked at 96 texts per call). `inputType` is required.\n *\n * The SDK call lives behind a protected `invokeModel` seam so tests can\n * subclass and inject canned response bodies without a real AWS request, and\n * the AWS SDK itself is imported lazily (it's Node/server-only).\n */\nexport class BedrockEmbeddingAdapter<\n TModel extends BedrockEmbeddingModel,\n // Same rationale as the text adapters: the base parameterises\n // `TProviderOptions extends object`, and the per-model options interfaces\n // lack implicit index signatures — `Record<string, any>` (not `unknown`)\n // accepts them. Confined to the generic constraint; no value cast.\n TProviderOptions extends Record<string, any> =\n ResolveEmbeddingProviderOptions<TModel>,\n> extends BaseEmbeddingAdapter<\n TModel,\n TProviderOptions,\n BedrockEmbeddingModelProviderOptionsByName,\n BedrockEmbeddingModelInputModalitiesByName\n> {\n readonly name = 'bedrock' as const\n private clientPromise?: Promise<BedrockRuntimeClient>\n private readonly clientConfig: BedrockEmbeddingConfig\n\n constructor(config: BedrockEmbeddingConfig, 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\n * held in a variable (not a string literal) so bundler dep scanners cannot\n * statically discover the AWS SDK and try to pre-bundle it for the browser.\n * Same pattern as the Converse text adapter.\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`, deferring\n * `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`. Bearer\n * auth needs `authSchemePreference` pinned or the SDK still tries SigV4\n * first — same reasoning as the Converse text adapter.\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 seam (overridden in tests so no real AWS call happens)\n // ---------------------------------------------------------------------------\n\n /** Send one InvokeModel call and parse its JSON response body. */\n protected async invokeModel(\n modelId: string,\n body: Record<string, unknown>,\n ): Promise<unknown> {\n const { InvokeModelCommand } = await this.importBedrockRuntime()\n const client = await this.getClient()\n const response = await client.send(\n new InvokeModelCommand({\n modelId,\n contentType: 'application/json',\n accept: 'application/json',\n body: JSON.stringify(body),\n }),\n )\n return JSON.parse(new TextDecoder().decode(response.body))\n }\n\n // ---------------------------------------------------------------------------\n // Public adapter surface\n // ---------------------------------------------------------------------------\n\n async createEmbeddings(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, logger } = options\n try {\n logger.request(\n `activity=embed provider=${this.name} model=${model} inputs=${options.input.length}`,\n { provider: this.name, model },\n )\n switch (model) {\n case 'amazon.titan-embed-text-v2:0':\n return await this.embedTitanText(options)\n case 'amazon.titan-embed-image-v1':\n return await this.embedTitanImage(options)\n case 'cohere.embed-english-v3':\n case 'cohere.embed-multilingual-v3':\n return await this.embedCohere(options)\n default:\n throw new Error(\n `Unknown Bedrock embedding model \"${model}\". Supported models: ` +\n `${BEDROCK_EMBEDDING_MODELS.join(', ')}.`,\n )\n }\n } catch (error: unknown) {\n logger.errors(`${this.name}.createEmbeddings fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),\n source: `${this.name}.createEmbeddings`,\n })\n throw error\n }\n }\n\n // ---------------------------------------------------------------------------\n // Per-model request mapping\n // ---------------------------------------------------------------------------\n\n /**\n * `amazon.titan-embed-text-v2:0` — one text per InvokeModel call, fanned\n * out with a concurrency cap; result order matches input order and per-call\n * `inputTextTokenCount`s are summed into usage.\n */\n private async embedTitanText(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, dimensions } = options\n if (\n dimensions !== undefined &&\n !TITAN_TEXT_DIMENSIONS.includes(dimensions)\n ) {\n throw new Error(\n `${model} supports dimensions 256, 512, or 1024; got ${dimensions}`,\n )\n }\n const normalize: boolean | undefined = options.modelOptions?.normalize\n const texts = requireTextOnlyEmbeddingInput(options.input, this.name, model)\n\n const responses = await mapWithConcurrency(\n texts,\n MAX_CONCURRENT_INVOCATIONS,\n async (text) => {\n // Built incrementally: exactOptionalPropertyTypes is on, and Titan\n // rejects explicit nulls/undefined for absent optional fields.\n const body: Record<string, unknown> = { inputText: text }\n if (dimensions !== undefined) body.dimensions = dimensions\n if (normalize !== undefined) body.normalize = normalize\n return readTitanEmbeddingBody(\n await this.invokeModel(model, body),\n `${this.name} ${model}`,\n )\n },\n )\n\n return this.toTitanResult(model, responses)\n }\n\n /**\n * `amazon.titan-embed-image-v1` (Titan Multimodal) — one item per\n * InvokeModel call. An item may carry text, an image, or both (a fused\n * item embedded into a single vector). Titan accepts at most one image per\n * request and never fetches remote URLs.\n */\n private async embedTitanImage(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, dimensions } = options\n const outputEmbeddingLength = dimensions ?? TITAN_IMAGE_DEFAULT_DIMENSIONS\n if (!TITAN_IMAGE_DIMENSIONS.includes(outputEmbeddingLength)) {\n throw new Error(\n `${model} supports dimensions 256, 384, or 1024; got ${outputEmbeddingLength}`,\n )\n }\n const items = resolveEmbeddingInput(options.input)\n\n const responses = await mapWithConcurrency(\n items,\n MAX_CONCURRENT_INVOCATIONS,\n async (item, index) => {\n if (item.images.length > 1) {\n throw new Error(\n `${model} accepts at most one image per input item; input item ` +\n `at index ${index} contains ${item.images.length} images. ` +\n `Pass them as separate input items (one vector each).`,\n )\n }\n const body: Record<string, unknown> = {\n embeddingConfig: { outputEmbeddingLength },\n }\n if (item.texts.length > 0) body.inputText = item.texts.join('\\n')\n const image = item.images[0]\n if (image) body.inputImage = toTitanInputImage(image, model)\n return readTitanEmbeddingBody(\n await this.invokeModel(model, body),\n `${this.name} ${model}`,\n )\n },\n )\n\n return this.toTitanResult(model, responses)\n }\n\n /**\n * `cohere.embed-*-v3` — natively batched (chunked at 96 texts per call,\n * order preserved across chunks). `inputType` is required by the Cohere\n * API; output dimensionality is fixed, so `dimensions` is rejected.\n */\n private async embedCohere(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, dimensions } = options\n if (dimensions !== undefined) {\n throw new Error(\n `${model} does not support the dimensions option; its output size is fixed`,\n )\n }\n const inputType: string | undefined = options.modelOptions?.inputType\n if (inputType === undefined) {\n throw new Error(\n `${model} requires modelOptions.inputType ('search_document' | ` +\n `'search_query' | 'classification' | 'clustering')`,\n )\n }\n const truncate: string | undefined = options.modelOptions?.truncate\n const texts = requireTextOnlyEmbeddingInput(options.input, this.name, model)\n const batches = chunk(texts, COHERE_MAX_BATCH_SIZE)\n\n const responses = await mapWithConcurrency(\n batches,\n MAX_CONCURRENT_INVOCATIONS,\n async (batch) => {\n const body: Record<string, unknown> = {\n texts: batch,\n input_type: inputType,\n }\n if (truncate !== undefined) body.truncate = truncate\n return readCohereEmbeddingBody(\n await this.invokeModel(model, body),\n `${this.name} ${model}`,\n )\n },\n )\n\n return {\n id: this.generateId(),\n model,\n embeddings: responses.flat().map((vector, index) => ({ vector, index })),\n }\n }\n\n /** Assemble an EmbeddingResult from per-item Titan responses. */\n private toTitanResult(\n model: string,\n responses: Array<TitanEmbeddingBody>,\n ): EmbeddingResult {\n let promptTokens = 0\n const embeddings = responses.map((response, index) => {\n promptTokens += response.inputTextTokenCount\n return { vector: response.embedding, index }\n })\n const usage: TokenUsage = {\n promptTokens,\n completionTokens: 0,\n totalTokens: promptTokens,\n }\n return { id: this.generateId(), model, embeddings, usage }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Response-body narrowing (SDK JSON boundary)\n// ---------------------------------------------------------------------------\n\ninterface TitanEmbeddingBody {\n embedding: Array<number>\n /** 0 when the response omits it (e.g. image-only Titan Multimodal calls). */\n inputTextTokenCount: number\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n/** Narrow a Titan InvokeModel JSON body: `{ embedding, inputTextTokenCount? }`. */\nfunction readTitanEmbeddingBody(\n raw: unknown,\n context: string,\n): TitanEmbeddingBody {\n const embedding =\n isRecord(raw) && Array.isArray(raw.embedding) ? raw.embedding : undefined\n if (!embedding) {\n throw new Error(\n `${context}: response body is missing the \"embedding\" array`,\n )\n }\n const inputTextTokenCount =\n isRecord(raw) && typeof raw.inputTextTokenCount === 'number'\n ? raw.inputTextTokenCount\n : 0\n return { embedding, inputTextTokenCount }\n}\n\n/** Narrow a Cohere InvokeModel JSON body: `{ embeddings: number[][] }` (float). */\nfunction readCohereEmbeddingBody(\n raw: unknown,\n context: string,\n): Array<Array<number>> {\n const embeddings =\n isRecord(raw) && Array.isArray(raw.embeddings) ? raw.embeddings : undefined\n if (!embeddings) {\n throw new Error(\n `${context}: response body is missing the \"embeddings\" array`,\n )\n }\n return embeddings\n}\n\n// ---------------------------------------------------------------------------\n// Input mapping helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map an ImagePart to Titan's `inputImage` (RAW base64, no data: prefix).\n * Accepts `data` sources as-is and `url` sources ONLY when the value is a\n * `data:` URI; Titan cannot fetch remote http(s) URLs.\n */\nfunction toTitanInputImage(image: ImagePart, model: string): string {\n const source = image.source\n if (isFileSource(source)) throw unsupportedFileSourceError('bedrock')\n if (source.type === 'data') {\n return source.value\n }\n if (source.value.startsWith('data:')) {\n const comma = source.value.indexOf(',')\n if (comma !== -1) {\n return source.value.slice(comma + 1)\n }\n }\n throw new Error(\n `Bedrock Titan does not fetch remote image URLs; pass base64 data ` +\n `(a { type: 'data' } source or a data: URI) for ${model}.`,\n )\n}\n\n/** Split into runs of at most `size`, preserving order. */\nfunction chunk<T>(items: Array<T>, size: number): Array<Array<T>> {\n const chunks: Array<Array<T>> = []\n for (let i = 0; i < items.length; i += size) {\n chunks.push(items.slice(i, i + size))\n }\n return chunks\n}\n\n/**\n * Map `fn` over `items` with at most `limit` calls in flight, returning\n * results in input order (result[i] corresponds to items[i] regardless of\n * completion order). Rejects with the first error.\n */\nasync function mapWithConcurrency<T, TResult>(\n items: ReadonlyArray<T>,\n limit: number,\n fn: (item: T, index: number) => Promise<TResult>,\n): Promise<Array<TResult>> {\n const results = new Array<TResult>(items.length)\n let next = 0\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const index = next++\n const item = items[index]\n if (item === undefined) continue // unreachable: index < length\n results[index] = await fn(item, index)\n }\n }\n const workerCount = Math.max(1, Math.min(limit, items.length))\n await Promise.all(Array.from({ length: workerCount }, () => worker()))\n return results\n}\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a Bedrock embedding adapter with an explicit API key (bearer).\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'amazon.titan-embed-text-v2:0')\n * @param apiKey - Your Bedrock API key\n * @param config - Optional additional configuration (region, baseURL, ...)\n * @returns Configured Bedrock embedding adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createBedrockEmbedding(\n * 'amazon.titan-embed-text-v2:0',\n * 'bedrock-api-key',\n * );\n *\n * const result = await embed({\n * adapter,\n * input: 'a red guitar',\n * });\n * ```\n */\nexport function createBedrockEmbedding<TModel extends BedrockEmbeddingModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<BedrockEmbeddingConfig, 'apiKey'>,\n): BedrockEmbeddingAdapter<TModel> {\n // Explicit apiKey is authoritative — spread config first so it can't override.\n return new BedrockEmbeddingAdapter({ ...config, apiKey }, model)\n}\n\n/**\n * Creates a Bedrock embedding adapter using the ambient auth cascade:\n * `config.apiKey` → `BEDROCK_API_KEY` → `AWS_BEARER_TOKEN_BEDROCK` → SigV4\n * (AWS credential provider chain). Auth resolves lazily on the first\n * request, so `auth: 'sigv4'` never requires an API key.\n *\n * @param model - The model name (e.g., 'cohere.embed-english-v3')\n * @param config - Optional configuration (region, auth, baseURL, ...)\n * @returns Configured Bedrock embedding adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = bedrockEmbedding('cohere.embed-english-v3');\n *\n * const result = await embed({\n * adapter,\n * input: ['a red guitar', 'a blue drum kit'],\n * modelOptions: { inputType: 'search_document' },\n * });\n *\n * console.log(result.embeddings[0].vector)\n * ```\n */\nexport function bedrockEmbedding<TModel extends BedrockEmbeddingModel>(\n model: TModel,\n config?: BedrockEmbeddingConfig,\n): BedrockEmbeddingAdapter<TModel> {\n return new BedrockEmbeddingAdapter(config ?? {}, model)\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { normalizeSystemPrompts } from "@tanstack/ai";
1
+ import { isFileSource, normalizeSystemPrompts, unsupportedFileSourceError } from "@tanstack/ai";
2
2
  //#region src/converse/message-converter.ts
3
3
  function base64ToBytes(b64) {
4
4
  return new Uint8Array(Buffer.from(b64, "base64"));
@@ -49,6 +49,7 @@ function contentPartToBlock(part, docIndex) {
49
49
  if (isTextPart(part)) return { text: part.content };
50
50
  if (isImagePart(part)) {
51
51
  const { source } = part;
52
+ if (isFileSource(source)) throw unsupportedFileSourceError("bedrock");
52
53
  if (!isDataSource(source)) throw new Error("Bedrock Converse requires inline image bytes; URL image sources are not supported.");
53
54
  return { image: {
54
55
  format: imageFormat(source.mimeType),
@@ -57,6 +58,7 @@ function contentPartToBlock(part, docIndex) {
57
58
  }
58
59
  if (isDocumentPart(part)) {
59
60
  const { source } = part;
61
+ if (isFileSource(source)) throw unsupportedFileSourceError("bedrock");
60
62
  if (!isDataSource(source)) throw new Error("Bedrock Converse requires inline document bytes; URL document sources are not supported.");
61
63
  return { document: {
62
64
  format: documentFormat(source.mimeType),
@@ -1 +1 @@
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(\n content: string | null | undefined | Array<ContentPart>,\n): string {\n if (content === null || content === undefined) 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
+ {"version":3,"file":"message-converter.js","names":[],"sources":["../../../src/converse/message-converter.ts"],"sourcesContent":["import {\n isFileSource,\n normalizeSystemPrompts,\n unsupportedFileSourceError,\n} 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(\n content: string | null | undefined | Array<ContentPart>,\n): string {\n if (content === null || content === undefined) 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 (isFileSource(source)) throw unsupportedFileSourceError('bedrock')\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 (isFileSource(source)) throw unsupportedFileSourceError('bedrock')\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-bedrock",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
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.59.0"
46
+ "@tanstack/ai": "^0.61.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "@aws-crypto/sha256-js": "^5.2.0",
@@ -52,7 +52,7 @@
52
52
  "@smithy/signature-v4": "^5.4.5",
53
53
  "@smithy/types": "^4.14.2",
54
54
  "openai": "^6.41.0",
55
- "@tanstack/openai-base": "^0.10.16"
55
+ "@tanstack/openai-base": "^0.11.1"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "vite build",
@@ -1,8 +1,10 @@
1
1
  import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'
2
2
  import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
3
3
  import {
4
+ isFileSource,
4
5
  requireTextOnlyEmbeddingInput,
5
6
  resolveEmbeddingInput,
7
+ unsupportedFileSourceError,
6
8
  } from '@tanstack/ai'
7
9
  import { resolveBedrockAuth } from '../utils/auth'
8
10
  import { BEDROCK_EMBEDDING_MODELS } from '../model-meta'
@@ -432,6 +434,7 @@ function readCohereEmbeddingBody(
432
434
  */
433
435
  function toTitanInputImage(image: ImagePart, model: string): string {
434
436
  const source = image.source
437
+ if (isFileSource(source)) throw unsupportedFileSourceError('bedrock')
435
438
  if (source.type === 'data') {
436
439
  return source.value
437
440
  }
@@ -1,4 +1,8 @@
1
- import { normalizeSystemPrompts } from '@tanstack/ai'
1
+ import {
2
+ isFileSource,
3
+ normalizeSystemPrompts,
4
+ unsupportedFileSourceError,
5
+ } from '@tanstack/ai'
2
6
  import type {
3
7
  ContentPart,
4
8
  ContentPartDataSource,
@@ -108,6 +112,7 @@ function contentPartToBlock(part: ContentPart, docIndex: number): ContentBlock {
108
112
 
109
113
  if (isImagePart(part)) {
110
114
  const { source } = part
115
+ if (isFileSource(source)) throw unsupportedFileSourceError('bedrock')
111
116
  if (!isDataSource(source)) {
112
117
  throw new Error(
113
118
  'Bedrock Converse requires inline image bytes; URL image sources are not supported.',
@@ -123,6 +128,7 @@ function contentPartToBlock(part: ContentPart, docIndex: number): ContentBlock {
123
128
 
124
129
  if (isDocumentPart(part)) {
125
130
  const { source } = part
131
+ if (isFileSource(source)) throw unsupportedFileSourceError('bedrock')
126
132
  if (!isDataSource(source)) {
127
133
  throw new Error(
128
134
  'Bedrock Converse requires inline document bytes; URL document sources are not supported.',