@amjadiqbal/tanstack-ai-sdk-bridge 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.2] - 2026-10-23
9
+
10
+ ### Changed
11
+
12
+ - Positioned as a migration stepping stone, following TanStack AI maintainers' feedback ([TanStack/ai#1884](https://github.com/TanStack/ai/pull/1884)): the README now starts with TanStack AI's migration guide, a table of the native adapter for each tested provider, a third-party notice, and the list of what the bridge loses versus a native adapter.
13
+ - **Peer range narrowed to `@tanstack/ai >=1.0.0 <1.1.0`** (the versions tested), because the bridge imports `@tanstack/ai/adapter-internals`, which has no stability promise. Newer TanStack AI versions are flagged at install time until a tested release widens the range.
14
+ - `aiSdkSpeech`: Ogg audio is reported as `opus` only when the stream is Opus, otherwise as `ogg`; an `audio/ogg` header maps to `ogg`, and the content type for `opus` and `ogg` is `audio/ogg`.
15
+
16
+ ### Documentation
17
+
18
+ - The `application/octet-stream` fallback for speech `contentType` is documented.
19
+
20
+ ## [0.1.1] - 2026-10-23
21
+
22
+ ### Fixed
23
+
24
+ - `aiSdkSpeech`: the result's `format` and `contentType` now come from what the provider returned (its `content-type` header, else the audio bytes), then the requested format, else `unknown`. Before, it reported `mp3` whenever no format was requested.
25
+
26
+ ### Added
27
+
28
+ - `aiSdkImage`: `modelOptions.mask` is sent to AI SDK v3/v4 image models (it was never sent before).
29
+
30
+ ### Documentation
31
+
32
+ - README states exactly which activities can be aborted (not `embed()`), what `outputSchema` does in the bridge (one non-streaming call without tools, JSON parsing, error cases; `chat()` validates), how `dimensions` behaves on v2/v3 embedding models, which transcription options are not sent, how speech reports its format, the image options and defaults, why `@ai-sdk/provider` is a dependency, and the `@tanstack/ai/adapter-internals` import.
33
+ - Removed the untested `deepgram` keyword.
34
+
8
35
  ## [0.1.0] - 2026-10-23
9
36
 
10
37
  ### Added
package/README.md CHANGED
@@ -5,14 +5,35 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
- # TanStack AI SDK Bridge: use Vercel AI SDK providers with TanStack AI
8
+ # TanStack AI SDK Bridge: a migration stepping stone from Vercel AI SDK providers to TanStack AI
9
9
 
10
- Use any Vercel AI SDK provider (`@ai-sdk/*` and community providers) as a [TanStack AI](https://tanstack.com/ai) adapter. The bridge reads the AI SDK provider spec (v2, v3 and v4), so a provider package works with `chat()`, `embed()`, `generateTranscription()`, `generateSpeech()`, `generateImage()` and `rerank()` without a dedicated TanStack adapter. It is also the shortest path for moving an AI SDK app to TanStack AI: keep your provider setup and change the rest step by step.
10
+ Run your existing Vercel AI SDK provider setup (`@ai-sdk/*` and community providers) inside [TanStack AI](https://tanstack.com/ai) **while you move to native TanStack AI adapters**. Wrap a model, e.g. `aiSdkText(deepseek('deepseek-chat'))`, and `chat()`, `embed()`, `generateTranscription()`, `generateSpeech()`, `generateImage()` and `rerank()` accept it. You can move your server routes and client to TanStack AI first, ship, and then swap each provider for its native adapter.
11
11
 
12
- Tested with: `@ai-sdk/deepseek`, `@ai-sdk/anthropic`, `@ai-sdk/openai`, `@ai-sdk/togetherai`, `@ai-sdk/cerebras`, `@ai-sdk/fireworks`, `@ai-sdk/cohere` (rerank), `ollama-ai-provider-v2` and `@ai-sdk/openai-compatible` 1.x, 2.x and 3.x (AI SDK 5, 6 and 7). See [What is tested](#what-is-tested).
12
+ **Use native TanStack AI adapters wherever one exists.** Start from TanStack AI's [migration guide from the Vercel AI SDK](https://tanstack.com/ai/latest/docs/migration/migration-from-vercel-ai) and its [adapter list](https://tanstack.com/ai/latest/docs/adapters/openai). For the providers this package was tested with:
13
+
14
+ | AI SDK provider | Native TanStack AI adapter |
15
+ |---|---|
16
+ | `@ai-sdk/openai` | `openaiText`, `openaiEmbedding`, `openaiImage`, `openaiSpeech`, `openaiTranscription` from `@tanstack/ai-openai` |
17
+ | `@ai-sdk/anthropic` | `anthropicText` from `@tanstack/ai-anthropic` |
18
+ | `ollama-ai-provider-v2` | `ollamaText`, `ollamaEmbedding` from `@tanstack/ai-ollama` |
19
+ | `@ai-sdk/cohere` | `cohereRerank`, `cohereEmbedding` from `@tanstack/ai-cohere` |
20
+ | `@ai-sdk/deepseek`, `@ai-sdk/togetherai`, `@ai-sdk/cerebras`, `@ai-sdk/fireworks`, `@ai-sdk/openai-compatible` | `openaiCompatible` from `@tanstack/ai-openai/compatible` ([docs](https://tanstack.com/ai/latest/docs/adapters/openai-compatible)) |
21
+
22
+ > [!IMPORTANT]
23
+ > **Third-party package.** It is not made, listed or endorsed by TanStack; TanStack AI's maintainers [declined to list it](https://github.com/TanStack/ai/pull/1884) in their docs. Problems with a bridged provider are this package's problems, not TanStack AI's: please [open an issue here](https://github.com/amjadiqbal/tanstack-ai-sdk-bridge/issues).
24
+
25
+ ### What you lose versus a native adapter
26
+
27
+ - **No per-model typing**: `modelOptions` and input modalities are not typed per model; you get one generic set of AI SDK call settings.
28
+ - **Reasoning only on AI SDK 7 providers** (spec v4). On AI SDK 5 and 6 providers `chat({ reasoning })` is not sent; reasoning output still streams if the provider returns it.
29
+ - **No provider-executed tools** (a provider's own web search, code execution and the like); only your function tools.
30
+ - **No reasoning signatures across turns**: signed or redacted thinking is not sent back, so providers that need it in later turns get plain text or nothing.
31
+ - **No `wrapFetch`**: TanStack AI's fetch middleware stack does not apply; pass a custom `fetch` to the provider factory instead.
32
+ - **No streaming structured output**: `outputSchema` runs one non-streaming call after the agent loop.
33
+ - **Depends on TanStack AI internals**: it imports `@tanstack/ai/adapter-internals`, which has **no stability promise**. The peer range is pinned to the versions tested (`@tanstack/ai >=1.0.0 <1.1.0`), so an untested TanStack AI version is flagged at install time (npm reports a peer conflict, pnpm a warning) instead of surfacing as a run-time error. A release that widens the range follows after testing.
13
34
 
14
35
  [![npm](https://img.shields.io/npm/v/@amjadiqbal/tanstack-ai-sdk-bridge?style=flat-square)](https://www.npmjs.com/package/@amjadiqbal/tanstack-ai-sdk-bridge)
15
- [![TanStack AI](https://img.shields.io/badge/TanStack%20AI-1.x-8B5CF6?style=flat-square)](https://tanstack.com/ai)
36
+ [![TanStack AI](https://img.shields.io/badge/TanStack%20AI-1.0.x-8B5CF6?style=flat-square)](https://tanstack.com/ai)
16
37
  [![License](https://img.shields.io/npm/l/@amjadiqbal/tanstack-ai-sdk-bridge?style=flat-square)](LICENSE.md)
17
38
 
18
39
  ```ts
@@ -20,25 +41,29 @@ import { chat } from '@tanstack/ai'
20
41
  import { aiSdkText } from '@amjadiqbal/tanstack-ai-sdk-bridge'
21
42
  import { deepseek } from '@ai-sdk/deepseek'
22
43
 
44
+ // Step 1 of a migration: keep the AI SDK provider, move to TanStack AI's chat().
23
45
  const stream = chat({
24
46
  adapter: aiSdkText(deepseek('deepseek-chat')),
25
47
  messages: [{ role: 'user', content: 'Hello!' }],
26
48
  threadId: 'thread-1',
27
49
  })
50
+ // Step 2: swap to the native adapter (openaiCompatible from @tanstack/ai-openai/compatible for DeepSeek).
28
51
  ```
29
52
 
30
- - **Text**: streaming chat, tool calls (TanStack's agent loop runs your tools), reasoning, structured output (`outputSchema`), token usage, abort.
53
+ - **Text**: streaming chat, tool calls (TanStack's agent loop runs your tools), reasoning, structured output (`outputSchema`), token usage, abort (see [Abort](#abort)).
31
54
  - **Embeddings, transcription, speech, image generation, rerank**: one adapter function each.
32
55
  - **AI SDK 5, 6 and 7 providers**: provider spec v2, v3 and v4 are all read.
33
- - **No extra runtime code from AI SDK**: only the provider package you already use, plus `@ai-sdk/provider` types. No `ai` package needed.
56
+ - **No AI SDK runtime code**: the built package imports only `@tanstack/ai`. `@ai-sdk/provider` is a dependency because the published type declarations use its model types; nothing from it runs. No `ai` package needed.
57
+
58
+ Tested with (local wire-level mock, no live APIs): `@ai-sdk/deepseek`, `@ai-sdk/anthropic`, `@ai-sdk/openai`, `@ai-sdk/togetherai`, `@ai-sdk/cerebras`, `@ai-sdk/fireworks`, `@ai-sdk/cohere` (rerank), `ollama-ai-provider-v2` and `@ai-sdk/openai-compatible` 1.x, 2.x and 3.x (AI SDK 5, 6 and 7). See [What is tested](#what-is-tested).
34
59
 
35
60
  ## Install
36
61
 
37
62
  ```bash
38
- npm i @amjadiqbal/tanstack-ai-sdk-bridge @tanstack/ai
63
+ npm i @amjadiqbal/tanstack-ai-sdk-bridge @tanstack/ai@1.0
39
64
  ```
40
65
 
41
- Then the AI SDK provider you want, for example `npm i @ai-sdk/deepseek`. Requires `@tanstack/ai` 1.x and Node 20+.
66
+ Then the AI SDK provider you already use, for example `npm i @ai-sdk/deepseek`. Requires `@tanstack/ai` 1.0.x (tested: 1.0.0) and Node 20+.
42
67
 
43
68
  ## Usage
44
69
 
@@ -112,7 +137,13 @@ const product = await chat({
112
137
  // product: { name: string; price: number }
113
138
  ```
114
139
 
115
- The bridge asks the model for JSON with the schema (AI SDK `responseFormat: { type: 'json', schema }`) and TanStack AI validates the result. A JSON answer wrapped in a Markdown code fence is also accepted.
140
+ What the bridge does for `outputSchema`, exactly:
141
+
142
+ - After the agent loop, TanStack AI calls the adapter's `structuredOutput()`. The bridge makes **one non-streaming** `doGenerate` call with the conversation, **without tools**, and `responseFormat: { type: 'json', schema }` (the JSON Schema TanStack AI made from your schema). Whether the provider enforces the schema depends on the provider.
143
+ - It joins the text parts of the answer and parses them as JSON. An answer wrapped in a Markdown code fence is accepted.
144
+ - It throws `Structured output generation failed: ...` (original error as `cause`) when the answer was cut off at the token limit, when the text is not JSON, or when the provider call fails.
145
+ - The bridge does not check the data against the schema; `chat()` does that with your schema after the bridge returns.
146
+ - `structuredOutputStream` and combined tools-plus-schema in one request are not implemented, so TanStack AI uses its non-streaming path.
116
147
 
117
148
  ### Reasoning
118
149
 
@@ -141,7 +172,7 @@ const { embeddings } = await embed({
141
172
  })
142
173
  ```
143
174
 
144
- Large inputs are split by the model's `maxEmbeddingsPerCall`. `dimensions` works with AI SDK 7 models; older ones take it through `modelOptions.providerOptions`.
175
+ Large inputs are split by the model's `maxEmbeddingsPerCall`. `dimensions` is sent to AI SDK 7 (spec v4) embedding models. On spec v2/v3 models the bridge throws if `dimensions` is set, because their spec has no such field: leave it unset and pass the provider's own option instead, for example `modelOptions: { providerOptions: { openai: { dimensions: 512 } } }`.
145
176
 
146
177
  ### Transcription, speech, image, rerank
147
178
 
@@ -168,11 +199,27 @@ await rerank({ adapter: aiSdkRerank(cohere.reranking('rerank-v3.5')), query: 're
168
199
  | `generateImage()` | `aiSdkImage(model)` | `ImageModel` | v2, v3, v4 |
169
200
  | `rerank()` | `aiSdkRerank(model)` | `RerankingModel` | v3, v4 |
170
201
 
202
+ Activity-specific `modelOptions`, on top of `providerOptions` and `headers`:
203
+
204
+ | Adapter | `modelOptions` | Notes |
205
+ |---|---|---|
206
+ | `aiSdkTranscription` | `mediaType` | Media type of the audio when it can't be read from a `Blob`/`File` type or a data URL. Default `audio/mpeg`. `language`, `prompt` and `responseFormat` are not sent (the AI SDK spec has no such fields); the result has `text`, plus `segments`, `language` and `duration` when the provider returns them. An `http(s)` URL as `audio` is downloaded with the global `fetch`, not the provider's `fetch`. |
207
+ | `aiSdkSpeech` | `instructions`, `language` | Sent as the AI SDK `instructions` and `language` call options. The result's `format` and `contentType` come from the provider's `content-type` header, else from the audio bytes (MP3, WAV, FLAC, AAC, and Ogg: `opus` when the Ogg stream is Opus, else `ogg`), else the `format` you asked for, else `unknown`. When the format is `unknown` (or a format with no known media type), `contentType` is `application/octet-stream`. |
208
+ | `aiSdkImage` | `aspectRatio`, `seed`, `mask` | Sent as the AI SDK call options. `mask` is an AI SDK image file (`{ type: 'file', mediaType, data }` or `{ type: 'url', url }`). Image parts in the prompt become input `files`. Input images and masks need a spec v3/v4 model; v2 models throw. When the model does not report `maxImagesPerCall`, one image is requested per call (the AI SDK's own default) until `numberOfImages` is reached. |
209
+
171
210
  Every function takes an optional second argument `{ name }`, the adapter name TanStack AI shows in devtools and usage. It defaults to the provider id before the first dot (`deepseek.chat` becomes `deepseek`).
172
211
 
212
+ ### Abort
213
+
214
+ | Activity | How to cancel | Passed to the AI SDK model |
215
+ |---|---|---|
216
+ | `chat()` | `abortController` (or the `request` signal) | yes: `abortSignal`, and the stream is cancelled |
217
+ | `generateTranscription()`, `generateSpeech()`, `generateImage()`, `rerank()` | `abortSignal` | yes |
218
+ | `embed()` | none: TanStack AI's `embed()` takes no signal | no |
219
+
173
220
  ## Migrating from the Vercel AI SDK
174
221
 
175
- TanStack's own [migration guide](https://tanstack.com/ai/latest/docs/migration/migration-from-vercel-ai) swaps your provider packages for TanStack adapters. With the bridge you can skip that step, and keep providers that have no TanStack adapter:
222
+ Follow TanStack's own [migration guide](https://tanstack.com/ai/latest/docs/migration/migration-from-vercel-ai); it ends with your provider packages swapped for native TanStack AI adapters. The bridge lets you do that last step later, one provider at a time:
176
223
 
177
224
  | Vercel AI SDK | TanStack AI with the bridge |
178
225
  |---|---|
@@ -187,7 +234,7 @@ TanStack's own [migration guide](https://tanstack.com/ai/latest/docs/migration/m
187
234
  | `useChat` from `@ai-sdk/react` | `useChat` from `@tanstack/ai-react` |
188
235
  | `result.toUIMessageStreamResponse()` | `toServerSentEventsResponse(stream)` |
189
236
 
190
- A practical order: move the server route and client first with the bridge, ship, then replace a bridged provider with a native TanStack adapter later if one exists and you need its typed per-model options.
237
+ Order: move the server route and client first with the bridge, ship, then replace each bridged provider with its native TanStack AI adapter (see the table at the top), and remove this package when none is left.
191
238
 
192
239
  ## What is tested
193
240
 
@@ -211,11 +258,13 @@ Everything below ran on 2026-10-23 with `@tanstack/ai` 1.0.0. **No live provider
211
258
 
212
259
  ## Limits
213
260
 
214
- - **Provider-executed tools** (an AI SDK provider's own web search, code execution and the like) are not forwarded; function tools are.
215
- - **Reasoning signatures** are not sent back, so providers that need signed thinking blocks in later turns (Anthropic extended thinking with tools) receive the reasoning as plain text or drop it.
216
- - `chat({ wrapFetch })` is not applied; pass a custom `fetch` to the provider factory (`createDeepSeek({ fetch })`).
261
+ The [What you lose versus a native adapter](#what-you-lose-versus-a-native-adapter) list at the top, plus:
262
+
263
+
217
264
  - Provider file handles (`{ type: 'file' }` sources) are rejected; send images and documents as data or URLs.
218
- - Transcription `language` and `prompt` have no AI SDK equivalent; pass them in `modelOptions.providerOptions`.
265
+ - Transcription `language`, `prompt` and `responseFormat` are not sent; see the options table.
266
+ - Embeddings cannot be aborted (TanStack AI's `embed()` has no signal).
267
+ - **TanStack AI internals:** the bridge imports `transformMessagesForReplay`, `tanstackMetadata` and `toRunErrorPayload` from `@tanstack/ai/adapter-internals`. That subpath has no stability promise (TanStack AI's maintainers say so), so the peer range is `>=1.0.0 <1.1.0`, the versions tested. Newer TanStack AI releases are flagged at install time until a tested release widens the range.
219
268
  - AI SDK reranking models report no token usage, so `rerank()` usage is zero.
220
269
  - Video, audio generation and realtime models are not bridged yet.
221
270
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AdapterYieldChunk, EmbeddingOptions, EmbeddingResult, ImageGenerationOptions, ImageGenerationResult, ReasoningLevel, RerankAdapterResult, RerankOptions, TTSOptions, TTSResult, TextOptions, TranscriptionOptions, TranscriptionResult } from "@tanstack/ai";
2
2
  import { BaseEmbeddingAdapter, BaseImageAdapter, BaseRerankAdapter, BaseTTSAdapter, BaseTextAdapter, BaseTranscriptionAdapter, StructuredOutputOptions, StructuredOutputResult } from "@tanstack/ai/adapters";
3
- import { EmbeddingModelV2, EmbeddingModelV3, EmbeddingModelV4, ImageModelV2, ImageModelV3, ImageModelV4, LanguageModelV2, LanguageModelV3, LanguageModelV4, RerankingModelV3, RerankingModelV4, SpeechModelV2, SpeechModelV3, SpeechModelV4, TranscriptionModelV2, TranscriptionModelV3, TranscriptionModelV4 } from "@ai-sdk/provider";
3
+ import { EmbeddingModelV2, EmbeddingModelV3, EmbeddingModelV4, ImageModelV2, ImageModelV3, ImageModelV4, ImageModelV4File, LanguageModelV2, LanguageModelV3, LanguageModelV4, RerankingModelV3, RerankingModelV4, SpeechModelV2, SpeechModelV3, SpeechModelV4, TranscriptionModelV2, TranscriptionModelV3, TranscriptionModelV4 } from "@ai-sdk/provider";
4
4
  /** AI SDK provider spec versions this package reads. */
5
5
  type SpecVersion = 'v2' | 'v3' | 'v4';
6
6
  /** Options every bridge adapter takes besides the model. */
@@ -45,7 +45,10 @@ type AiSdkImageModel = ImageModelV2 | ImageModelV3 | ImageModelV4;
45
45
  interface AiSdkImageModelOptions extends AiSdkCallOptions {
46
46
  /** Aspect ratio such as `16:9`, for models that take one instead of a size. */
47
47
  aspectRatio?: `${number}:${number}`;
48
+ /** Seed, for models that take one. */
48
49
  seed?: number;
50
+ /** Mask for inpainting (AI SDK v3/v4 image models that support masks), as an AI SDK image file. */
51
+ mask?: ImageModelV4File;
49
52
  }
50
53
  /**
51
54
  * TanStack AI image adapter around an AI SDK image model.
@@ -98,7 +101,12 @@ interface AiSdkSpeechModelOptions extends AiSdkCallOptions {
98
101
  /** Language of the text, e.g. `en`. */
99
102
  language?: string;
100
103
  }
101
- /** TanStack AI text-to-speech adapter around an AI SDK speech model. */
104
+ /**
105
+ * TanStack AI text-to-speech adapter around an AI SDK speech model.
106
+ * `format` in the result is what the provider returned (content-type header or audio bytes),
107
+ * falling back to the requested format, else `unknown`. `contentType` is the header value, else the media type of the
108
+ * format, else `application/octet-stream`.
109
+ */
102
110
  export declare class AiSdkSpeechAdapter<TModel extends string = string> extends BaseTTSAdapter<TModel, AiSdkSpeechModelOptions> {
103
111
  readonly name: string;
104
112
  readonly speechModel: AiSdkSpeechModel;
@@ -177,7 +185,9 @@ interface AiSdkTranscriptionModelOptions extends AiSdkCallOptions {
177
185
  }
178
186
  /**
179
187
  * TanStack AI transcription adapter around an AI SDK transcription model.
180
- * `language` and `prompt` have no AI SDK equivalent: pass them in `modelOptions.providerOptions`.
188
+ * `language`, `prompt` and `responseFormat` have no AI SDK equivalent and are not sent: pass the provider's own
189
+ * options in `modelOptions.providerOptions`. The result always has `text`, plus `segments`, `language` and `duration`
190
+ * when the provider returns them. An `http(s)` URL as `audio` is downloaded with the global `fetch`, not the provider's.
181
191
  */
182
192
  export declare class AiSdkTranscriptionAdapter<TModel extends string = string> extends BaseTranscriptionAdapter<TModel, AiSdkTranscriptionModelOptions> {
183
193
  readonly name: string;
package/dist/index.js CHANGED
@@ -177,6 +177,7 @@ var AiSdkImageAdapter = class extends BaseImageAdapter {
177
177
  const wanted = options.numberOfImages ?? 1;
178
178
  try {
179
179
  const { text, files } = this.promptOf(options.prompt);
180
+ if (modelOptions?.mask && this.specificationVersion === "v2") throw new Error(`${this.name}: a mask needs an AI SDK v3 or v4 image model`);
180
181
  const maxSetting = this.imageModel.maxImagesPerCall;
181
182
  const max = (typeof maxSetting === "function" ? await maxSetting({ modelId: this.imageModel.modelId }) : maxSetting) ?? 1;
182
183
  logger.request(`activity=image provider=${this.name} model=${model} n=${wanted} size=${size ?? "default"}`, {
@@ -196,7 +197,7 @@ var AiSdkImageAdapter = class extends BaseImageAdapter {
196
197
  aspectRatio: modelOptions?.aspectRatio,
197
198
  seed: modelOptions?.seed,
198
199
  files: files.length > 0 ? files : void 0,
199
- mask: void 0,
200
+ mask: modelOptions?.mask,
200
201
  providerOptions: modelOptions?.providerOptions ?? {},
201
202
  ...modelOptions?.headers && { headers: modelOptions.headers },
202
203
  ...abortSignal && { abortSignal }
@@ -300,13 +301,51 @@ const SPECS$2 = [
300
301
  ];
301
302
  const CONTENT_TYPES = {
302
303
  mp3: "audio/mpeg",
303
- opus: "audio/opus",
304
+ opus: "audio/ogg",
305
+ ogg: "audio/ogg",
304
306
  aac: "audio/aac",
305
307
  flac: "audio/flac",
306
308
  wav: "audio/wav",
307
309
  pcm: "audio/pcm"
308
310
  };
309
- /** TanStack AI text-to-speech adapter around an AI SDK speech model. */
311
+ const FORMATS_BY_TYPE = {
312
+ "audio/mpeg": "mp3",
313
+ "audio/mp3": "mp3",
314
+ "audio/wav": "wav",
315
+ "audio/x-wav": "wav",
316
+ "audio/wave": "wav",
317
+ "audio/opus": "opus",
318
+ "audio/ogg": "ogg",
319
+ "audio/aac": "aac",
320
+ "audio/flac": "flac",
321
+ "audio/x-flac": "flac",
322
+ "audio/pcm": "pcm",
323
+ "audio/l16": "pcm"
324
+ };
325
+ function headerValue(headers, name) {
326
+ if (!headers || typeof headers !== "object") return void 0;
327
+ for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === name && typeof value === "string") return value;
328
+ }
329
+ function formatFromContentType(type) {
330
+ return type ? FORMATS_BY_TYPE[type.toLowerCase()] : void 0;
331
+ }
332
+ /** Recognises MP3, WAV, Ogg (Opus or other), FLAC and ADTS AAC from their first bytes. */
333
+ function formatFromBytes(bytes) {
334
+ const ascii = (start, text) => [...text].every((char, i) => bytes[start + i] === char.charCodeAt(0));
335
+ if (ascii(0, "RIFF") && ascii(8, "WAVE")) return "wav";
336
+ if (ascii(0, "OggS")) return ascii(27 + (bytes[26] ?? 0), "OpusHead") ? "opus" : "ogg";
337
+ if (ascii(0, "fLaC")) return "flac";
338
+ if (ascii(0, "ID3")) return "mp3";
339
+ const b0 = bytes[0] ?? 0;
340
+ const b1 = bytes[1] ?? 0;
341
+ if (b0 === 255 && (b1 & 224) === 224) return (b1 & 6) === 0 ? "aac" : "mp3";
342
+ }
343
+ /**
344
+ * TanStack AI text-to-speech adapter around an AI SDK speech model.
345
+ * `format` in the result is what the provider returned (content-type header or audio bytes),
346
+ * falling back to the requested format, else `unknown`. `contentType` is the header value, else the media type of the
347
+ * format, else `application/octet-stream`.
348
+ */
310
349
  var AiSdkSpeechAdapter = class extends BaseTTSAdapter {
311
350
  name;
312
351
  speechModel;
@@ -333,15 +372,16 @@ var AiSdkSpeechAdapter = class extends BaseTTSAdapter {
333
372
  ...modelOptions?.language !== void 0 && { language: modelOptions.language },
334
373
  ...callExtras(modelOptions, abortSignal)
335
374
  });
375
+ const bytes = typeof result.audio === "string" ? base64ToBytes(result.audio) : result.audio;
336
376
  const audio = typeof result.audio === "string" ? result.audio : bytesToBase64(result.audio);
337
- const outputFormat = format ?? "mp3";
338
- const headerType = (result.response?.headers)?.["content-type"];
377
+ const headerType = headerValue(result.response?.headers, "content-type")?.split(";")[0]?.trim();
378
+ const outputFormat = formatFromContentType(headerType) ?? formatFromBytes(bytes) ?? format ?? "unknown";
339
379
  return {
340
380
  id: randomId(this.name),
341
381
  model: result.response?.modelId ?? model,
342
382
  audio,
343
383
  format: outputFormat,
344
- contentType: headerType?.split(";")[0] || CONTENT_TYPES[outputFormat] || "application/octet-stream"
384
+ contentType: headerType || CONTENT_TYPES[outputFormat] || "application/octet-stream"
345
385
  };
346
386
  } catch (error) {
347
387
  logger.errors(`${this.name}.generateSpeech fatal`, {
@@ -1050,7 +1090,9 @@ async function toAudio(audio, fallbackType, signal) {
1050
1090
  }
1051
1091
  /**
1052
1092
  * TanStack AI transcription adapter around an AI SDK transcription model.
1053
- * `language` and `prompt` have no AI SDK equivalent: pass them in `modelOptions.providerOptions`.
1093
+ * `language`, `prompt` and `responseFormat` have no AI SDK equivalent and are not sent: pass the provider's own
1094
+ * options in `modelOptions.providerOptions`. The result always has `text`, plus `segments`, `language` and `duration`
1095
+ * when the provider returns them. An `http(s)` URL as `audio` is downloaded with the global `fetch`, not the provider's.
1054
1096
  */
1055
1097
  var AiSdkTranscriptionAdapter = class extends BaseTranscriptionAdapter {
1056
1098
  name;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@amjadiqbal/tanstack-ai-sdk-bridge",
3
- "version": "0.1.0",
4
- "description": "Use any Vercel AI SDK provider (@ai-sdk/* and community providers) as a TanStack AI adapter: streaming chat with tools, reasoning and structured output, plus embeddings, transcription, speech, image generation and rerank. Supports AI SDK provider spec v2, v3 and v4.",
3
+ "version": "0.1.2",
4
+ "description": "Third-party migration stepping stone: run Vercel AI SDK provider models (@ai-sdk/* and community providers) inside TanStack AI while you move to native TanStack AI adapters. Chat with tools, reasoning and structured output, plus embeddings, transcription, speech, image and rerank. Not affiliated with TanStack.",
5
5
  "keywords": [
6
6
  "tanstack",
7
7
  "tanstack-ai",
@@ -13,7 +13,6 @@
13
13
  "cerebras",
14
14
  "togetherai",
15
15
  "fireworks",
16
- "deepgram",
17
16
  "embeddings",
18
17
  "tool-calling",
19
18
  "migration"
@@ -58,7 +57,7 @@
58
57
  "@ai-sdk/provider": "^4.0.32"
59
58
  },
60
59
  "peerDependencies": {
61
- "@tanstack/ai": "^1.0.0"
60
+ "@tanstack/ai": ">=1.0.0 <1.1.0"
62
61
  },
63
62
  "devDependencies": {
64
63
  "@ai-sdk/anthropic": "4.0.86",