@tanstack/ai-cloudflare 0.0.0 → 0.1.0

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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -2
  3. package/dist/esm/adapters/embedding.d.ts +19 -0
  4. package/dist/esm/adapters/embedding.js +60 -0
  5. package/dist/esm/adapters/embedding.js.map +1 -0
  6. package/dist/esm/adapters/image.d.ts +26 -0
  7. package/dist/esm/adapters/image.js +62 -0
  8. package/dist/esm/adapters/image.js.map +1 -0
  9. package/dist/esm/adapters/summarize.d.ts +15 -0
  10. package/dist/esm/adapters/summarize.js +22 -0
  11. package/dist/esm/adapters/summarize.js.map +1 -0
  12. package/dist/esm/adapters/text.d.ts +71 -0
  13. package/dist/esm/adapters/text.js +88 -0
  14. package/dist/esm/adapters/text.js.map +1 -0
  15. package/dist/esm/adapters/transcription.d.ts +21 -0
  16. package/dist/esm/adapters/transcription.js +134 -0
  17. package/dist/esm/adapters/transcription.js.map +1 -0
  18. package/dist/esm/adapters/tts.d.ts +26 -0
  19. package/dist/esm/adapters/tts.js +68 -0
  20. package/dist/esm/adapters/tts.js.map +1 -0
  21. package/dist/esm/byok.d.ts +9 -0
  22. package/dist/esm/byok.js +24 -0
  23. package/dist/esm/byok.js.map +1 -0
  24. package/dist/esm/gateway.d.ts +30 -0
  25. package/dist/esm/gateway.js +45 -0
  26. package/dist/esm/gateway.js.map +1 -0
  27. package/dist/esm/index.d.ts +16 -0
  28. package/dist/esm/index.js +8 -0
  29. package/dist/esm/utils/config.d.ts +60 -0
  30. package/dist/esm/utils/config.js +48 -0
  31. package/dist/esm/utils/config.js.map +1 -0
  32. package/dist/esm/utils/fetch.d.ts +27 -0
  33. package/dist/esm/utils/fetch.js +94 -0
  34. package/dist/esm/utils/fetch.js.map +1 -0
  35. package/dist/esm/utils/models.d.ts +16 -0
  36. package/dist/esm/utils/run.d.ts +21 -0
  37. package/dist/esm/utils/run.js +62 -0
  38. package/dist/esm/utils/run.js.map +1 -0
  39. package/package.json +73 -4
  40. package/src/adapters/embedding.ts +84 -0
  41. package/src/adapters/image.ts +97 -0
  42. package/src/adapters/summarize.ts +46 -0
  43. package/src/adapters/text.ts +150 -0
  44. package/src/adapters/transcription.ts +227 -0
  45. package/src/adapters/tts.ts +100 -0
  46. package/src/byok.ts +21 -0
  47. package/src/gateway.ts +57 -0
  48. package/src/index.ts +68 -0
  49. package/src/utils/config.ts +129 -0
  50. package/src/utils/fetch.ts +131 -0
  51. package/src/utils/models.ts +41 -0
  52. package/src/utils/run.ts +116 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tanner Linsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,54 @@
1
- # @tanstack/ai-cloudflare
1
+ <div align="center">
2
+ <picture>
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://tanstack.com/api/readme/ai.png?theme=dark"
6
+ />
7
+ <source
8
+ media="(prefers-color-scheme: light)"
9
+ srcset="https://tanstack.com/api/readme/ai.png"
10
+ />
11
+ <img
12
+ src="https://tanstack.com/api/readme/ai.png"
13
+ alt="TanStack AI"
14
+ width="900"
15
+ />
16
+ </picture>
17
+ </div>
2
18
 
3
- Placeholder package for npm OIDC trusted publishing setup. Not a functional package. Do not depend on this version.
19
+ <br />
20
+
21
+ # @tanstack/ai-cloudflare
22
+
23
+ Cloudflare adapter for TanStack AI: Workers AI chat, summarization, embeddings, image generation, text-to-speech, and transcription over the `env.AI` binding or the REST API, with AI Gateway routing for any provider.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @tanstack/ai @tanstack/ai-cloudflare
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```typescript
34
+ import { chat, toServerSentEventsResponse } from '@tanstack/ai'
35
+ import { createCloudflareText } from '@tanstack/ai-cloudflare'
36
+ import type { Ai } from '@cloudflare/workers-types'
37
+
38
+ export default {
39
+ async fetch(request: Request, env: { AI: Ai }) {
40
+ const { messages } = await request.json()
41
+ const stream = chat({
42
+ adapter: createCloudflareText('@cf/zai-org/glm-5.3-flash', {
43
+ binding: env.AI,
44
+ }),
45
+ messages,
46
+ })
47
+ return toServerSentEventsResponse(stream)
48
+ },
49
+ }
50
+ ```
51
+
52
+ Outside a Worker, pass `{ accountId, apiKey }` or set `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN` and use `cloudflareText(model)`.
53
+
54
+ See the [docs](https://tanstack.com/ai/latest/docs/adapters/cloudflare) for AI Gateway, media adapters, and model options.
@@ -0,0 +1,19 @@
1
+ import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters';
2
+ import { EmbeddingOptions, EmbeddingResult } from '@tanstack/ai';
3
+ import { CloudflareConfig, CloudflareConfigInput } from '../utils/config.js';
4
+ import { CloudflareEmbeddingModel } from '../utils/models.js';
5
+ /** Extra inputs forwarded to the embedding model (model specific). */
6
+ export type CloudflareEmbeddingProviderOptions = Record<string, unknown>;
7
+ /**
8
+ * Cloudflare embedding adapter. Runs Workers AI text-embedding models
9
+ * (`{ text: [...] }` in, `{ data: number[][] }` out) through the binding or
10
+ * the REST API.
11
+ */
12
+ export declare class CloudflareEmbeddingAdapter<TModel extends CloudflareEmbeddingModel> extends BaseEmbeddingAdapter<TModel, CloudflareEmbeddingProviderOptions> {
13
+ private readonly cfConfig;
14
+ readonly name: "cloudflare";
15
+ constructor(cfConfig: CloudflareConfig, model: TModel);
16
+ createEmbeddings(options: EmbeddingOptions<CloudflareEmbeddingProviderOptions>): Promise<EmbeddingResult>;
17
+ }
18
+ export declare function createCloudflareEmbedding<TModel extends CloudflareEmbeddingModel>(model: TModel, config: CloudflareConfig): CloudflareEmbeddingAdapter<TModel>;
19
+ export declare function cloudflareEmbedding<TModel extends CloudflareEmbeddingModel>(model: TModel, config?: CloudflareConfigInput): CloudflareEmbeddingAdapter<TModel>;
@@ -0,0 +1,60 @@
1
+ import { resolveConfigFromEnv } from "../utils/config.js";
2
+ import { runModel } from "../utils/run.js";
3
+ import { generateId } from "@tanstack/ai-utils";
4
+ import { BaseEmbeddingAdapter } from "@tanstack/ai/adapters";
5
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
6
+ import { requireTextOnlyEmbeddingInput } from "@tanstack/ai";
7
+ //#region src/adapters/embedding.ts
8
+ /**
9
+ * Cloudflare embedding adapter. Runs Workers AI text-embedding models
10
+ * (`{ text: [...] }` in, `{ data: number[][] }` out) through the binding or
11
+ * the REST API.
12
+ */
13
+ var CloudflareEmbeddingAdapter = class extends BaseEmbeddingAdapter {
14
+ cfConfig;
15
+ name = "cloudflare";
16
+ constructor(cfConfig, model) {
17
+ super(model, {});
18
+ this.cfConfig = cfConfig;
19
+ }
20
+ async createEmbeddings(options) {
21
+ const { model, logger } = options;
22
+ const texts = requireTextOnlyEmbeddingInput(options.input, this.name, model);
23
+ if (options.dimensions !== void 0) throw new Error("Workers AI embedding models have fixed dimensions; do not set `dimensions`");
24
+ try {
25
+ logger.request(`activity=embed provider=${this.name} model=${model} inputs=${texts.length}`, {
26
+ provider: this.name,
27
+ model
28
+ });
29
+ const output = await runModel(this.cfConfig, model, {
30
+ ...options.modelOptions,
31
+ text: texts
32
+ });
33
+ if (!Array.isArray(output.data) || output.data.length !== texts.length) throw new Error(`Workers AI ${model} returned ${output.data?.length ?? 0} embeddings for ${texts.length} inputs`);
34
+ return {
35
+ id: generateId(this.name),
36
+ model,
37
+ embeddings: output.data.map((vector, index) => ({
38
+ vector,
39
+ index
40
+ }))
41
+ };
42
+ } catch (error) {
43
+ logger.errors(`${this.name}.createEmbeddings fatal`, {
44
+ error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),
45
+ source: `${this.name}.createEmbeddings`
46
+ });
47
+ throw error;
48
+ }
49
+ }
50
+ };
51
+ function createCloudflareEmbedding(model, config) {
52
+ return new CloudflareEmbeddingAdapter(config, model);
53
+ }
54
+ function cloudflareEmbedding(model, config) {
55
+ return new CloudflareEmbeddingAdapter(resolveConfigFromEnv(config), model);
56
+ }
57
+ //#endregion
58
+ export { CloudflareEmbeddingAdapter, cloudflareEmbedding, createCloudflareEmbedding };
59
+
60
+ //# sourceMappingURL=embedding.js.map
@@ -0,0 +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 { generateId } from '@tanstack/ai-utils'\nimport { requireTextOnlyEmbeddingInput } from '@tanstack/ai'\nimport { resolveConfigFromEnv } from '../utils/config'\nimport { runModel } from '../utils/run'\nimport type { EmbeddingOptions, EmbeddingResult } from '@tanstack/ai'\nimport type { CloudflareConfig, CloudflareConfigInput } from '../utils/config'\nimport type { CloudflareEmbeddingModel } from '../utils/models'\n\n/** Extra inputs forwarded to the embedding model (model specific). */\nexport type CloudflareEmbeddingProviderOptions = Record<string, unknown>\n\n/**\n * Cloudflare embedding adapter. Runs Workers AI text-embedding models\n * (`{ text: [...] }` in, `{ data: number[][] }` out) through the binding or\n * the REST API.\n */\nexport class CloudflareEmbeddingAdapter<\n TModel extends CloudflareEmbeddingModel,\n> extends BaseEmbeddingAdapter<TModel, CloudflareEmbeddingProviderOptions> {\n readonly name = 'cloudflare' as const\n\n constructor(\n private readonly cfConfig: CloudflareConfig,\n model: TModel,\n ) {\n super(model, {})\n }\n\n async createEmbeddings(\n options: EmbeddingOptions<CloudflareEmbeddingProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, logger } = options\n const texts = requireTextOnlyEmbeddingInput(options.input, this.name, model)\n if (options.dimensions !== undefined) {\n throw new Error(\n 'Workers AI embedding models have fixed dimensions; do not set `dimensions`',\n )\n }\n try {\n logger.request(\n `activity=embed provider=${this.name} model=${model} inputs=${texts.length}`,\n { provider: this.name, model },\n )\n const output = (await runModel(this.cfConfig, model, {\n ...options.modelOptions,\n text: texts,\n })) as { data?: Array<Array<number>> }\n if (!Array.isArray(output.data) || output.data.length !== texts.length) {\n throw new Error(\n `Workers AI ${model} returned ${output.data?.length ?? 0} embeddings for ${texts.length} inputs`,\n )\n }\n return {\n id: generateId(this.name),\n model,\n embeddings: output.data.map((vector, index) => ({\n vector,\n index,\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\nexport function createCloudflareEmbedding<\n TModel extends CloudflareEmbeddingModel,\n>(model: TModel, config: CloudflareConfig): CloudflareEmbeddingAdapter<TModel> {\n return new CloudflareEmbeddingAdapter(config, model)\n}\n\nexport function cloudflareEmbedding<TModel extends CloudflareEmbeddingModel>(\n model: TModel,\n config?: CloudflareConfigInput,\n): CloudflareEmbeddingAdapter<TModel> {\n return new CloudflareEmbeddingAdapter(resolveConfigFromEnv(config), model)\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,IAAa,6BAAb,cAEU,qBAAiE;CAItD;CAHnB,OAAgB;CAEhB,YACE,UACA,OACA;EACA,MAAM,OAAO,CAAC,CAAC;EAHE,KAAA,WAAA;CAInB;CAEA,MAAM,iBACJ,SAC0B;EAC1B,MAAM,EAAE,OAAO,WAAW;EAC1B,MAAM,QAAQ,8BAA8B,QAAQ,OAAO,KAAK,MAAM,KAAK;EAC3E,IAAI,QAAQ,eAAe,KAAA,GACzB,MAAM,IAAI,MACR,4EACF;EAEF,IAAI;GACF,OAAO,QACL,2BAA2B,KAAK,KAAK,SAAS,MAAM,UAAU,MAAM,UACpE;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GACA,MAAM,SAAU,MAAM,SAAS,KAAK,UAAU,OAAO;IACnD,GAAG,QAAQ;IACX,MAAM;GACR,CAAC;GACD,IAAI,CAAC,MAAM,QAAQ,OAAO,IAAI,KAAK,OAAO,KAAK,WAAW,MAAM,QAC9D,MAAM,IAAI,MACR,cAAc,MAAM,YAAY,OAAO,MAAM,UAAU,EAAE,kBAAkB,MAAM,OAAO,QAC1F;GAEF,OAAO;IACL,IAAI,WAAW,KAAK,IAAI;IACxB;IACA,YAAY,OAAO,KAAK,KAAK,QAAQ,WAAW;KAC9C;KACA;IACF,EAAE;GACJ;EACF,SAAS,OAAgB;GACvB,OAAO,OAAO,GAAG,KAAK,KAAK,0BAA0B;IACnD,OAAO,kBAAkB,OAAO,GAAG,KAAK,KAAK,yBAAyB;IACtE,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;AACF;AAEA,SAAgB,0BAEd,OAAe,QAA8D;CAC7E,OAAO,IAAI,2BAA2B,QAAQ,KAAK;AACrD;AAEA,SAAgB,oBACd,OACA,QACoC;CACpC,OAAO,IAAI,2BAA2B,qBAAqB,MAAM,GAAG,KAAK;AAC3E"}
@@ -0,0 +1,26 @@
1
+ import { BaseImageAdapter } from '@tanstack/ai/adapters';
2
+ import { ImageGenerationOptions, ImageGenerationResult } from '@tanstack/ai';
3
+ import { CloudflareConfig, CloudflareConfigInput } from '../utils/config.js';
4
+ import { CloudflareImageModel } from '../utils/models.js';
5
+ /** Text-to-image inputs forwarded to the model (`steps`, `guidance`, ...). */
6
+ export interface CloudflareImageProviderOptions {
7
+ negative_prompt?: string;
8
+ steps?: number;
9
+ num_steps?: number;
10
+ guidance?: number;
11
+ seed?: number;
12
+ [key: string]: unknown;
13
+ }
14
+ /**
15
+ * Cloudflare image adapter. Runs Workers AI text-to-image models and returns
16
+ * base64 images, whether the model answers with `{ image }` JSON (Flux,
17
+ * Leonardo) or raw PNG bytes (Stable Diffusion).
18
+ */
19
+ export declare class CloudflareImageAdapter<TModel extends CloudflareImageModel> extends BaseImageAdapter<TModel, CloudflareImageProviderOptions> {
20
+ private readonly cfConfig;
21
+ readonly name: "cloudflare";
22
+ constructor(cfConfig: CloudflareConfig, model: TModel);
23
+ generateImages(options: ImageGenerationOptions<CloudflareImageProviderOptions>): Promise<ImageGenerationResult>;
24
+ }
25
+ export declare function createCloudflareImage<TModel extends CloudflareImageModel>(model: TModel, config: CloudflareConfig): CloudflareImageAdapter<TModel>;
26
+ export declare function cloudflareImage<TModel extends CloudflareImageModel>(model: TModel, config?: CloudflareConfigInput): CloudflareImageAdapter<TModel>;
@@ -0,0 +1,62 @@
1
+ import { resolveConfigFromEnv } from "../utils/config.js";
2
+ import { outputToBase64, runModel } from "../utils/run.js";
3
+ import { generateId } from "@tanstack/ai-utils";
4
+ import { BaseImageAdapter } from "@tanstack/ai/adapters";
5
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
6
+ import { resolveMediaPrompt } from "@tanstack/ai";
7
+ //#region src/adapters/image.ts
8
+ /**
9
+ * Cloudflare image adapter. Runs Workers AI text-to-image models and returns
10
+ * base64 images, whether the model answers with `{ image }` JSON (Flux,
11
+ * Leonardo) or raw PNG bytes (Stable Diffusion).
12
+ */
13
+ var CloudflareImageAdapter = class extends BaseImageAdapter {
14
+ cfConfig;
15
+ name = "cloudflare";
16
+ constructor(cfConfig, model) {
17
+ super(model, {});
18
+ this.cfConfig = cfConfig;
19
+ }
20
+ async generateImages(options) {
21
+ const { model, logger, numberOfImages = 1 } = options;
22
+ const prompt = resolveMediaPrompt(options.prompt);
23
+ const [width, height] = options.size?.split("x").map(Number) ?? [];
24
+ const inputs = {
25
+ ...width && { width },
26
+ ...height && { height },
27
+ ...options.modelOptions,
28
+ prompt: prompt.text
29
+ };
30
+ try {
31
+ logger.request(`activity=image provider=${this.name} model=${model} n=${numberOfImages}`, {
32
+ provider: this.name,
33
+ model
34
+ });
35
+ const images = await Promise.all(Array.from({ length: numberOfImages }, async () => {
36
+ const output = await runModel(this.cfConfig, model, inputs, { signal: options.abortSignal });
37
+ return { b64Json: output && typeof output === "object" && "image" in output ? output.image : await outputToBase64(output) };
38
+ }));
39
+ return {
40
+ id: generateId(this.name),
41
+ model,
42
+ images
43
+ };
44
+ } catch (error) {
45
+ logger.errors(`${this.name}.generateImages fatal`, {
46
+ error: toRunErrorPayload(error, `${this.name}.generateImages failed`),
47
+ source: `${this.name}.generateImages`
48
+ });
49
+ throw error;
50
+ }
51
+ }
52
+ };
53
+ function createCloudflareImage(model, config) {
54
+ return new CloudflareImageAdapter(config, model);
55
+ }
56
+ function cloudflareImage(model, config) {
57
+ return new CloudflareImageAdapter(resolveConfigFromEnv(config), model);
58
+ }
59
+ //#endregion
60
+ export { CloudflareImageAdapter, cloudflareImage, createCloudflareImage };
61
+
62
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","names":[],"sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport { resolveMediaPrompt } from '@tanstack/ai'\nimport { resolveConfigFromEnv } from '../utils/config'\nimport { outputToBase64, runModel } from '../utils/run'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n} from '@tanstack/ai'\nimport type { CloudflareConfig, CloudflareConfigInput } from '../utils/config'\nimport type { CloudflareImageModel } from '../utils/models'\n\n/** Text-to-image inputs forwarded to the model (`steps`, `guidance`, ...). */\nexport interface CloudflareImageProviderOptions {\n negative_prompt?: string\n steps?: number\n num_steps?: number\n guidance?: number\n seed?: number\n [key: string]: unknown\n}\n\n/**\n * Cloudflare image adapter. Runs Workers AI text-to-image models and returns\n * base64 images, whether the model answers with `{ image }` JSON (Flux,\n * Leonardo) or raw PNG bytes (Stable Diffusion).\n */\nexport class CloudflareImageAdapter<\n TModel extends CloudflareImageModel,\n> extends BaseImageAdapter<TModel, CloudflareImageProviderOptions> {\n readonly name = 'cloudflare' as const\n\n constructor(\n private readonly cfConfig: CloudflareConfig,\n model: TModel,\n ) {\n super(model, {})\n }\n\n async generateImages(\n options: ImageGenerationOptions<CloudflareImageProviderOptions>,\n ): Promise<ImageGenerationResult> {\n const { model, logger, numberOfImages = 1 } = options\n const prompt = resolveMediaPrompt(options.prompt)\n const [width, height] = options.size?.split('x').map(Number) ?? []\n const inputs = {\n ...(width && { width }),\n ...(height && { height }),\n ...options.modelOptions,\n prompt: prompt.text,\n }\n try {\n logger.request(\n `activity=image provider=${this.name} model=${model} n=${numberOfImages}`,\n { provider: this.name, model },\n )\n const images = await Promise.all(\n Array.from(\n { length: numberOfImages },\n async (): Promise<GeneratedImage> => {\n const output = await runModel(this.cfConfig, model, inputs, {\n signal: options.abortSignal,\n })\n const image =\n output && typeof output === 'object' && 'image' in output\n ? (output as { image: string }).image\n : await outputToBase64(output)\n return { b64Json: image }\n },\n ),\n )\n return { id: generateId(this.name), model, images }\n } catch (error: unknown) {\n logger.errors(`${this.name}.generateImages fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateImages failed`),\n source: `${this.name}.generateImages`,\n })\n throw error\n }\n }\n}\n\nexport function createCloudflareImage<TModel extends CloudflareImageModel>(\n model: TModel,\n config: CloudflareConfig,\n): CloudflareImageAdapter<TModel> {\n return new CloudflareImageAdapter(config, model)\n}\n\nexport function cloudflareImage<TModel extends CloudflareImageModel>(\n model: TModel,\n config?: CloudflareConfigInput,\n): CloudflareImageAdapter<TModel> {\n return new CloudflareImageAdapter(resolveConfigFromEnv(config), model)\n}\n"],"mappings":";;;;;;;;;;;;AA6BA,IAAa,yBAAb,cAEU,iBAAyD;CAI9C;CAHnB,OAAgB;CAEhB,YACE,UACA,OACA;EACA,MAAM,OAAO,CAAC,CAAC;EAHE,KAAA,WAAA;CAInB;CAEA,MAAM,eACJ,SACgC;EAChC,MAAM,EAAE,OAAO,QAAQ,iBAAiB,MAAM;EAC9C,MAAM,SAAS,mBAAmB,QAAQ,MAAM;EAChD,MAAM,CAAC,OAAO,UAAU,QAAQ,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,KAAK,CAAC;EACjE,MAAM,SAAS;GACb,GAAI,SAAS,EAAE,MAAM;GACrB,GAAI,UAAU,EAAE,OAAO;GACvB,GAAG,QAAQ;GACX,QAAQ,OAAO;EACjB;EACA,IAAI;GACF,OAAO,QACL,2BAA2B,KAAK,KAAK,SAAS,MAAM,KAAK,kBACzD;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GACA,MAAM,SAAS,MAAM,QAAQ,IAC3B,MAAM,KACJ,EAAE,QAAQ,eAAe,GACzB,YAAqC;IACnC,MAAM,SAAS,MAAM,SAAS,KAAK,UAAU,OAAO,QAAQ,EAC1D,QAAQ,QAAQ,YAClB,CAAC;IAKD,OAAO,EAAE,SAHP,UAAU,OAAO,WAAW,YAAY,WAAW,SAC9C,OAA6B,QAC9B,MAAM,eAAe,MAAM,EACT;GAC1B,CACF,CACF;GACA,OAAO;IAAE,IAAI,WAAW,KAAK,IAAI;IAAG;IAAO;GAAO;EACpD,SAAS,OAAgB;GACvB,OAAO,OAAO,GAAG,KAAK,KAAK,wBAAwB;IACjD,OAAO,kBAAkB,OAAO,GAAG,KAAK,KAAK,uBAAuB;IACpE,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;AACF;AAEA,SAAgB,sBACd,OACA,QACgC;CAChC,OAAO,IAAI,uBAAuB,QAAQ,KAAK;AACjD;AAEA,SAAgB,gBACd,OACA,QACgC;CAChC,OAAO,IAAI,uBAAuB,qBAAqB,MAAM,GAAG,KAAK;AACvE"}
@@ -0,0 +1,15 @@
1
+ import { ChatStreamSummarizeAdapter, InferTextProviderOptions } from '@tanstack/ai/adapters';
2
+ import { CloudflareTextAdapter } from './text.js';
3
+ import { CloudflareConfigInput, CloudflareTextConfig, CloudflareTextRestConfig } from '../utils/config.js';
4
+ import { CloudflareTextModel } from '../utils/models.js';
5
+ export type CloudflareSummarizeModel = CloudflareTextModel;
6
+ /**
7
+ * Creates a Cloudflare summarize adapter. Summaries run as a chat request
8
+ * against the given model.
9
+ */
10
+ export declare function createCloudflareSummarize<TModel extends CloudflareSummarizeModel>(model: TModel, config: CloudflareTextConfig): ChatStreamSummarizeAdapter<TModel, InferTextProviderOptions<CloudflareTextAdapter<TModel>>>;
11
+ /**
12
+ * Creates a Cloudflare summarize adapter, reading `CLOUDFLARE_ACCOUNT_ID` and
13
+ * `CLOUDFLARE_API_TOKEN` from the environment unless a binding is passed.
14
+ */
15
+ export declare function cloudflareSummarize<TModel extends CloudflareSummarizeModel>(model: TModel, config?: CloudflareConfigInput<CloudflareTextRestConfig>): ChatStreamSummarizeAdapter<TModel, InferTextProviderOptions<CloudflareTextAdapter<TModel>>>;
@@ -0,0 +1,22 @@
1
+ import { resolveConfigFromEnv } from "../utils/config.js";
2
+ import { CloudflareTextAdapter } from "./text.js";
3
+ import { ChatStreamSummarizeAdapter } from "@tanstack/ai/adapters";
4
+ //#region src/adapters/summarize.ts
5
+ /**
6
+ * Creates a Cloudflare summarize adapter. Summaries run as a chat request
7
+ * against the given model.
8
+ */
9
+ function createCloudflareSummarize(model, config) {
10
+ return new ChatStreamSummarizeAdapter(new CloudflareTextAdapter(config, model), model, "cloudflare");
11
+ }
12
+ /**
13
+ * Creates a Cloudflare summarize adapter, reading `CLOUDFLARE_ACCOUNT_ID` and
14
+ * `CLOUDFLARE_API_TOKEN` from the environment unless a binding is passed.
15
+ */
16
+ function cloudflareSummarize(model, config) {
17
+ return createCloudflareSummarize(model, resolveConfigFromEnv(config));
18
+ }
19
+ //#endregion
20
+ export { cloudflareSummarize, createCloudflareSummarize };
21
+
22
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.js","names":[],"sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'\nimport { resolveConfigFromEnv } from '../utils/config'\nimport { CloudflareTextAdapter } from './text'\nimport type { InferTextProviderOptions } from '@tanstack/ai/adapters'\nimport type {\n CloudflareConfigInput,\n CloudflareTextConfig,\n CloudflareTextRestConfig,\n} from '../utils/config'\nimport type { CloudflareTextModel } from '../utils/models'\n\nexport type CloudflareSummarizeModel = CloudflareTextModel\n\n/**\n * Creates a Cloudflare summarize adapter. Summaries run as a chat request\n * against the given model.\n */\nexport function createCloudflareSummarize<\n TModel extends CloudflareSummarizeModel,\n>(\n model: TModel,\n config: CloudflareTextConfig,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<CloudflareTextAdapter<TModel>>\n> {\n return new ChatStreamSummarizeAdapter(\n new CloudflareTextAdapter(config, model),\n model,\n 'cloudflare',\n )\n}\n\n/**\n * Creates a Cloudflare summarize adapter, reading `CLOUDFLARE_ACCOUNT_ID` and\n * `CLOUDFLARE_API_TOKEN` from the environment unless a binding is passed.\n */\nexport function cloudflareSummarize<TModel extends CloudflareSummarizeModel>(\n model: TModel,\n config?: CloudflareConfigInput<CloudflareTextRestConfig>,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<CloudflareTextAdapter<TModel>>\n> {\n return createCloudflareSummarize(model, resolveConfigFromEnv(config))\n}\n"],"mappings":";;;;;;;;AAiBA,SAAgB,0BAGd,OACA,QAIA;CACA,OAAO,IAAI,2BACT,IAAI,sBAAsB,QAAQ,KAAK,GACvC,OACA,YACF;AACF;;;;;AAMA,SAAgB,oBACd,OACA,QAIA;CACA,OAAO,0BAA0B,OAAO,qBAAqB,MAAM,CAAC;AACtE"}
@@ -0,0 +1,71 @@
1
+ import { OpenAIBaseChatCompletionsTextAdapter } from '@tanstack/openai-base';
2
+ import { ChatCompletionChunk, ChatCompletionMessageParam } from 'openai/resources/chat/completions/completions';
3
+ import { CloudflareConfigInput, CloudflareTextConfig, CloudflareTextRestConfig } from '../utils/config.js';
4
+ import { CloudflareTextModel } from '../utils/models.js';
5
+ import { ModelMessage } from '@tanstack/ai';
6
+ /**
7
+ * Chat Completions parameters forwarded verbatim to Workers AI. Reasoning
8
+ * models (GLM, Kimi, gpt-oss, QwQ) read `reasoning_effort` and
9
+ * `chat_template_kwargs`; `null` for `reasoning_effort` turns reasoning off.
10
+ */
11
+ export interface CloudflareTextProviderOptions {
12
+ temperature?: number;
13
+ max_tokens?: number;
14
+ top_p?: number;
15
+ top_k?: number;
16
+ seed?: number;
17
+ repetition_penalty?: number;
18
+ frequency_penalty?: number;
19
+ presence_penalty?: number;
20
+ reasoning_effort?: 'low' | 'medium' | 'high' | null;
21
+ chat_template_kwargs?: {
22
+ enable_thinking?: boolean;
23
+ clear_thinking?: boolean;
24
+ };
25
+ }
26
+ /**
27
+ * Cloudflare text (chat) adapter.
28
+ *
29
+ * Drives Workers AI's OpenAI-compatible Chat Completions surface with the
30
+ * OpenAI SDK. Inside a Worker pass `{ binding: env.AI }`; anywhere else pass
31
+ * `{ accountId, apiKey }`. Add `gateway` to route through AI Gateway. Any
32
+ * catalog model works, including third-party `provider/model` ids billed
33
+ * through AI Gateway.
34
+ */
35
+ export declare class CloudflareTextAdapter<TModel extends CloudflareTextModel, TProviderOptions extends Record<string, any> = CloudflareTextProviderOptions> extends OpenAIBaseChatCompletionsTextAdapter<TModel, TProviderOptions> {
36
+ readonly kind: "text";
37
+ readonly name: "cloudflare";
38
+ constructor(config: CloudflareTextConfig, model: TModel);
39
+ /**
40
+ * Workers AI validates `messages[].content` as a string, so a tool-call-only
41
+ * assistant turn (which OpenAI accepts as `content: null`) is sent as `''`.
42
+ */
43
+ protected convertMessage(message: ModelMessage): ChatCompletionMessageParam;
44
+ /**
45
+ * Workers AI accepts `response_format` next to `tools` but its models answer
46
+ * the tool follow-up turn in prose, so structured output with tools runs as
47
+ * a separate finalization request instead.
48
+ */
49
+ supportsCombinedToolsAndSchema(): boolean;
50
+ /** Workers AI reasoning models stream thinking as `reasoning_content` (some as `reasoning`). */
51
+ protected extractReasoning(chunk: ChatCompletionChunk): {
52
+ text: string;
53
+ } | undefined;
54
+ }
55
+ /**
56
+ * Creates a Cloudflare text adapter with explicit configuration.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // Inside a Worker
61
+ * const adapter = createCloudflareText('@cf/zai-org/glm-5.3-flash', { binding: env.AI })
62
+ * // Anywhere, over REST
63
+ * const adapter = createCloudflareText('@cf/zai-org/glm-5.3-flash', { accountId, apiKey })
64
+ * ```
65
+ */
66
+ export declare function createCloudflareText<TModel extends CloudflareTextModel>(model: TModel, config: CloudflareTextConfig): CloudflareTextAdapter<TModel>;
67
+ /**
68
+ * Creates a Cloudflare text adapter, reading `CLOUDFLARE_ACCOUNT_ID` and
69
+ * `CLOUDFLARE_API_TOKEN` from the environment unless a binding is passed.
70
+ */
71
+ export declare function cloudflareText<TModel extends CloudflareTextModel>(model: TModel, config?: CloudflareConfigInput<CloudflareTextRestConfig>): CloudflareTextAdapter<TModel>;
@@ -0,0 +1,88 @@
1
+ import { gatewayHeaders, isBindingConfig, resolveConfigFromEnv, restChatBaseURL } from "../utils/config.js";
2
+ import { createBindingFetch, createRestFetch } from "../utils/fetch.js";
3
+ import OpenAI from "openai";
4
+ import { OpenAIBaseChatCompletionsTextAdapter } from "@tanstack/openai-base";
5
+ //#region src/adapters/text.ts
6
+ function createClient(config) {
7
+ if (isBindingConfig(config)) return new OpenAI({
8
+ apiKey: "cloudflare-binding",
9
+ fetch: createBindingFetch(config.binding, config.gateway)
10
+ });
11
+ const { accountId: _accountId, binding: _binding, gateway, ...clientOptions } = config;
12
+ return new OpenAI({
13
+ ...clientOptions,
14
+ baseURL: restChatBaseURL(config),
15
+ defaultHeaders: {
16
+ ...gatewayHeaders(gateway),
17
+ ...clientOptions.defaultHeaders
18
+ },
19
+ fetch: createRestFetch(clientOptions.fetch)
20
+ });
21
+ }
22
+ /**
23
+ * Cloudflare text (chat) adapter.
24
+ *
25
+ * Drives Workers AI's OpenAI-compatible Chat Completions surface with the
26
+ * OpenAI SDK. Inside a Worker pass `{ binding: env.AI }`; anywhere else pass
27
+ * `{ accountId, apiKey }`. Add `gateway` to route through AI Gateway. Any
28
+ * catalog model works, including third-party `provider/model` ids billed
29
+ * through AI Gateway.
30
+ */
31
+ var CloudflareTextAdapter = class extends OpenAIBaseChatCompletionsTextAdapter {
32
+ kind = "text";
33
+ name = "cloudflare";
34
+ constructor(config, model) {
35
+ super(model, "cloudflare", createClient(config));
36
+ }
37
+ /**
38
+ * Workers AI validates `messages[].content` as a string, so a tool-call-only
39
+ * assistant turn (which OpenAI accepts as `content: null`) is sent as `''`.
40
+ */
41
+ convertMessage(message) {
42
+ const converted = super.convertMessage(message);
43
+ if (converted.role === "assistant" && converted.content == null) return {
44
+ ...converted,
45
+ content: ""
46
+ };
47
+ return converted;
48
+ }
49
+ /**
50
+ * Workers AI accepts `response_format` next to `tools` but its models answer
51
+ * the tool follow-up turn in prose, so structured output with tools runs as
52
+ * a separate finalization request instead.
53
+ */
54
+ supportsCombinedToolsAndSchema() {
55
+ return false;
56
+ }
57
+ /** Workers AI reasoning models stream thinking as `reasoning_content` (some as `reasoning`). */
58
+ extractReasoning(chunk) {
59
+ const delta = chunk.choices[0]?.delta;
60
+ const raw = delta?.reasoning_content ?? delta?.reasoning;
61
+ return typeof raw === "string" && raw.length > 0 ? { text: raw } : void 0;
62
+ }
63
+ };
64
+ /**
65
+ * Creates a Cloudflare text adapter with explicit configuration.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // Inside a Worker
70
+ * const adapter = createCloudflareText('@cf/zai-org/glm-5.3-flash', { binding: env.AI })
71
+ * // Anywhere, over REST
72
+ * const adapter = createCloudflareText('@cf/zai-org/glm-5.3-flash', { accountId, apiKey })
73
+ * ```
74
+ */
75
+ function createCloudflareText(model, config) {
76
+ return new CloudflareTextAdapter(config, model);
77
+ }
78
+ /**
79
+ * Creates a Cloudflare text adapter, reading `CLOUDFLARE_ACCOUNT_ID` and
80
+ * `CLOUDFLARE_API_TOKEN` from the environment unless a binding is passed.
81
+ */
82
+ function cloudflareText(model, config) {
83
+ return new CloudflareTextAdapter(resolveConfigFromEnv(config), model);
84
+ }
85
+ //#endregion
86
+ export { CloudflareTextAdapter, cloudflareText, createCloudflareText };
87
+
88
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","names":[],"sources":["../../../src/adapters/text.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { OpenAIBaseChatCompletionsTextAdapter } from '@tanstack/openai-base'\nimport {\n gatewayHeaders,\n isBindingConfig,\n resolveConfigFromEnv,\n restChatBaseURL,\n} from '../utils/config'\nimport { createBindingFetch, createRestFetch } from '../utils/fetch'\nimport type {\n ChatCompletionChunk,\n ChatCompletionMessageParam,\n} from 'openai/resources/chat/completions/completions'\nimport type {\n CloudflareConfigInput,\n CloudflareTextConfig,\n CloudflareTextRestConfig,\n} from '../utils/config'\nimport type { CloudflareTextModel } from '../utils/models'\nimport type { ModelMessage } from '@tanstack/ai'\n\n/**\n * Chat Completions parameters forwarded verbatim to Workers AI. Reasoning\n * models (GLM, Kimi, gpt-oss, QwQ) read `reasoning_effort` and\n * `chat_template_kwargs`; `null` for `reasoning_effort` turns reasoning off.\n */\nexport interface CloudflareTextProviderOptions {\n temperature?: number\n max_tokens?: number\n top_p?: number\n top_k?: number\n seed?: number\n repetition_penalty?: number\n frequency_penalty?: number\n presence_penalty?: number\n reasoning_effort?: 'low' | 'medium' | 'high' | null\n chat_template_kwargs?: {\n enable_thinking?: boolean\n clear_thinking?: boolean\n }\n}\n\nfunction createClient(config: CloudflareTextConfig): OpenAI {\n if (isBindingConfig(config)) {\n return new OpenAI({\n // The binding authenticates by itself; the SDK only requires a value.\n apiKey: 'cloudflare-binding',\n fetch: createBindingFetch(config.binding, config.gateway),\n })\n }\n const {\n accountId: _accountId,\n binding: _binding,\n gateway,\n ...clientOptions\n } = config\n return new OpenAI({\n ...clientOptions,\n baseURL: restChatBaseURL(config),\n defaultHeaders: {\n ...gatewayHeaders(gateway),\n ...clientOptions.defaultHeaders,\n },\n fetch: createRestFetch(clientOptions.fetch),\n })\n}\n\n/**\n * Cloudflare text (chat) adapter.\n *\n * Drives Workers AI's OpenAI-compatible Chat Completions surface with the\n * OpenAI SDK. Inside a Worker pass `{ binding: env.AI }`; anywhere else pass\n * `{ accountId, apiKey }`. Add `gateway` to route through AI Gateway. Any\n * catalog model works, including third-party `provider/model` ids billed\n * through AI Gateway.\n */\nexport class CloudflareTextAdapter<\n TModel extends CloudflareTextModel,\n TProviderOptions extends Record<string, any> = CloudflareTextProviderOptions,\n> extends OpenAIBaseChatCompletionsTextAdapter<TModel, TProviderOptions> {\n override readonly kind = 'text' as const\n override readonly name = 'cloudflare' as const\n\n constructor(config: CloudflareTextConfig, model: TModel) {\n super(model, 'cloudflare', createClient(config))\n }\n\n /**\n * Workers AI validates `messages[].content` as a string, so a tool-call-only\n * assistant turn (which OpenAI accepts as `content: null`) is sent as `''`.\n */\n protected override convertMessage(\n message: ModelMessage,\n ): ChatCompletionMessageParam {\n const converted = super.convertMessage(message)\n if (converted.role === 'assistant' && converted.content == null) {\n return { ...converted, content: '' }\n }\n return converted\n }\n\n /**\n * Workers AI accepts `response_format` next to `tools` but its models answer\n * the tool follow-up turn in prose, so structured output with tools runs as\n * a separate finalization request instead.\n */\n override supportsCombinedToolsAndSchema(): boolean {\n return false\n }\n\n /** Workers AI reasoning models stream thinking as `reasoning_content` (some as `reasoning`). */\n protected override extractReasoning(\n chunk: ChatCompletionChunk,\n ): { text: string } | undefined {\n const delta = chunk.choices[0]?.delta as\n | { reasoning?: unknown; reasoning_content?: unknown }\n | undefined\n const raw = delta?.reasoning_content ?? delta?.reasoning\n return typeof raw === 'string' && raw.length > 0 ? { text: raw } : undefined\n }\n}\n\n/**\n * Creates a Cloudflare text adapter with explicit configuration.\n *\n * @example\n * ```typescript\n * // Inside a Worker\n * const adapter = createCloudflareText('@cf/zai-org/glm-5.3-flash', { binding: env.AI })\n * // Anywhere, over REST\n * const adapter = createCloudflareText('@cf/zai-org/glm-5.3-flash', { accountId, apiKey })\n * ```\n */\nexport function createCloudflareText<TModel extends CloudflareTextModel>(\n model: TModel,\n config: CloudflareTextConfig,\n): CloudflareTextAdapter<TModel> {\n return new CloudflareTextAdapter(config, model)\n}\n\n/**\n * Creates a Cloudflare text adapter, reading `CLOUDFLARE_ACCOUNT_ID` and\n * `CLOUDFLARE_API_TOKEN` from the environment unless a binding is passed.\n */\nexport function cloudflareText<TModel extends CloudflareTextModel>(\n model: TModel,\n config?: CloudflareConfigInput<CloudflareTextRestConfig>,\n): CloudflareTextAdapter<TModel> {\n return new CloudflareTextAdapter(resolveConfigFromEnv(config), model)\n}\n"],"mappings":";;;;;AA0CA,SAAS,aAAa,QAAsC;CAC1D,IAAI,gBAAgB,MAAM,GACxB,OAAO,IAAI,OAAO;EAEhB,QAAQ;EACR,OAAO,mBAAmB,OAAO,SAAS,OAAO,OAAO;CAC1D,CAAC;CAEH,MAAM,EACJ,WAAW,YACX,SAAS,UACT,SACA,GAAG,kBACD;CACJ,OAAO,IAAI,OAAO;EAChB,GAAG;EACH,SAAS,gBAAgB,MAAM;EAC/B,gBAAgB;GACd,GAAG,eAAe,OAAO;GACzB,GAAG,cAAc;EACnB;EACA,OAAO,gBAAgB,cAAc,KAAK;CAC5C,CAAC;AACH;;;;;;;;;;AAWA,IAAa,wBAAb,cAGU,qCAA+D;CACvE,OAAyB;CACzB,OAAyB;CAEzB,YAAY,QAA8B,OAAe;EACvD,MAAM,OAAO,cAAc,aAAa,MAAM,CAAC;CACjD;;;;;CAMA,eACE,SAC4B;EAC5B,MAAM,YAAY,MAAM,eAAe,OAAO;EAC9C,IAAI,UAAU,SAAS,eAAe,UAAU,WAAW,MACzD,OAAO;GAAE,GAAG;GAAW,SAAS;EAAG;EAErC,OAAO;CACT;;;;;;CAOA,iCAAmD;EACjD,OAAO;CACT;;CAGA,iBACE,OAC8B;EAC9B,MAAM,QAAQ,MAAM,QAAQ,EAAE,EAAE;EAGhC,MAAM,MAAM,OAAO,qBAAqB,OAAO;EAC/C,OAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,EAAE,MAAM,IAAI,IAAI,KAAA;CACrE;AACF;;;;;;;;;;;;AAaA,SAAgB,qBACd,OACA,QAC+B;CAC/B,OAAO,IAAI,sBAAsB,QAAQ,KAAK;AAChD;;;;;AAMA,SAAgB,eACd,OACA,QAC+B;CAC/B,OAAO,IAAI,sBAAsB,qBAAqB,MAAM,GAAG,KAAK;AACtE"}
@@ -0,0 +1,21 @@
1
+ import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters';
2
+ import { TranscriptionOptions, TranscriptionResult } from '@tanstack/ai';
3
+ import { CloudflareConfig, CloudflareConfigInput } from '../utils/config.js';
4
+ import { CloudflareTranscriptionModel } from '../utils/models.js';
5
+ /** Extra inputs forwarded to the transcription model (model specific). */
6
+ export type CloudflareTranscriptionProviderOptions = Record<string, unknown>;
7
+ /**
8
+ * Cloudflare transcription adapter. Whisper models take base64 audio in the
9
+ * `audio` input; Deepgram Nova takes the raw bytes. Both return text plus
10
+ * timed words, and Whisper also returns segments.
11
+ */
12
+ export declare class CloudflareTranscriptionAdapter<TModel extends CloudflareTranscriptionModel> extends BaseTranscriptionAdapter<TModel, CloudflareTranscriptionProviderOptions> {
13
+ private readonly cfConfig;
14
+ readonly name: "cloudflare";
15
+ constructor(cfConfig: CloudflareConfig, model: TModel);
16
+ transcribe(options: TranscriptionOptions<CloudflareTranscriptionProviderOptions>): Promise<TranscriptionResult>;
17
+ private runWhisper;
18
+ private runNova;
19
+ }
20
+ export declare function createCloudflareTranscription<TModel extends CloudflareTranscriptionModel>(model: TModel, config: CloudflareConfig): CloudflareTranscriptionAdapter<TModel>;
21
+ export declare function cloudflareTranscription<TModel extends CloudflareTranscriptionModel>(model: TModel, config?: CloudflareConfigInput): CloudflareTranscriptionAdapter<TModel>;
@@ -0,0 +1,134 @@
1
+ import { isBindingConfig, resolveConfigFromEnv } from "../utils/config.js";
2
+ import { runModel } from "../utils/run.js";
3
+ import { arrayBufferToBase64, generateId } from "@tanstack/ai-utils";
4
+ import { BaseTranscriptionAdapter } from "@tanstack/ai/adapters";
5
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
6
+ //#region src/adapters/transcription.ts
7
+ async function toBytes(audio, fetchImpl, signal) {
8
+ if (typeof audio === "string") {
9
+ if (/^https?:\/\//.test(audio)) {
10
+ const response = await fetchImpl(audio, { signal });
11
+ if (!response.ok) throw new Error(`Could not fetch audio from ${audio} (${response.status})`);
12
+ return {
13
+ bytes: await response.arrayBuffer(),
14
+ contentType: response.headers.get("content-type") ?? "audio/mpeg"
15
+ };
16
+ }
17
+ const match = /^data:([^;]+);base64,(.*)$/.exec(audio);
18
+ const base64 = match?.[2] ?? audio;
19
+ const binary = atob(base64);
20
+ const bytes = new Uint8Array(binary.length);
21
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
22
+ return {
23
+ bytes: bytes.buffer,
24
+ contentType: match?.[1] ?? "audio/mpeg"
25
+ };
26
+ }
27
+ if (audio instanceof ArrayBuffer) return {
28
+ bytes: audio,
29
+ contentType: "audio/mpeg"
30
+ };
31
+ return {
32
+ bytes: await audio.arrayBuffer(),
33
+ contentType: audio.type || "audio/mpeg"
34
+ };
35
+ }
36
+ /**
37
+ * Cloudflare transcription adapter. Whisper models take base64 audio in the
38
+ * `audio` input; Deepgram Nova takes the raw bytes. Both return text plus
39
+ * timed words, and Whisper also returns segments.
40
+ */
41
+ var CloudflareTranscriptionAdapter = class extends BaseTranscriptionAdapter {
42
+ cfConfig;
43
+ name = "cloudflare";
44
+ constructor(cfConfig, model) {
45
+ super(model, {});
46
+ this.cfConfig = cfConfig;
47
+ }
48
+ async transcribe(options) {
49
+ const { model, logger, language, prompt } = options;
50
+ try {
51
+ logger.request(`activity=transcription provider=${this.name} model=${model}`, {
52
+ provider: this.name,
53
+ model
54
+ });
55
+ const fetchImpl = (isBindingConfig(this.cfConfig) ? void 0 : this.cfConfig.fetch) ?? fetch;
56
+ const { bytes, contentType } = await toBytes(options.audio, fetchImpl, options.abortSignal);
57
+ const output = model.startsWith("@cf/deepgram/") ? await this.runNova(model, bytes, contentType, options) : await this.runWhisper(model, bytes, {
58
+ language,
59
+ prompt,
60
+ ...options.modelOptions
61
+ }, options.abortSignal);
62
+ return {
63
+ id: generateId(this.name),
64
+ model,
65
+ ...output
66
+ };
67
+ } catch (error) {
68
+ logger.errors(`${this.name}.transcribe fatal`, {
69
+ error: toRunErrorPayload(error, `${this.name}.transcribe failed`),
70
+ source: `${this.name}.transcribe`
71
+ });
72
+ throw error;
73
+ }
74
+ }
75
+ async runWhisper(model, bytes, inputs, signal) {
76
+ const { language, prompt, ...rest } = inputs;
77
+ const output = await runModel(this.cfConfig, model, {
78
+ ...language && { language },
79
+ ...prompt && { initial_prompt: prompt },
80
+ ...rest,
81
+ audio: arrayBufferToBase64(bytes)
82
+ }, { signal });
83
+ if (typeof output.text !== "string") throw new Error(`Workers AI ${model} returned no transcript`);
84
+ return {
85
+ text: output.text,
86
+ language: output.transcription_info?.language,
87
+ duration: output.transcription_info?.duration,
88
+ segments: output.segments?.map((segment, id) => ({
89
+ id,
90
+ start: segment.start,
91
+ end: segment.end,
92
+ text: segment.text.trim()
93
+ })),
94
+ words: toWords(output.words)
95
+ };
96
+ }
97
+ async runNova(model, bytes, contentType, options) {
98
+ const output = await runModel(this.cfConfig, model, {
99
+ ...options.language && { language: options.language },
100
+ ...options.modelOptions
101
+ }, {
102
+ signal: options.abortSignal,
103
+ binary: {
104
+ field: "audio",
105
+ body: bytes,
106
+ contentType
107
+ }
108
+ });
109
+ const alternative = output.results?.channels?.[0]?.alternatives?.[0];
110
+ if (typeof alternative?.transcript !== "string") throw new Error(`Workers AI ${model} returned no transcript`);
111
+ return {
112
+ text: alternative.transcript,
113
+ duration: output.metadata?.duration,
114
+ words: toWords(alternative?.words)
115
+ };
116
+ }
117
+ };
118
+ function toWords(words) {
119
+ return words?.map((w) => ({
120
+ word: (w.word ?? "").trim(),
121
+ start: w.start ?? 0,
122
+ end: w.end ?? 0
123
+ }));
124
+ }
125
+ function createCloudflareTranscription(model, config) {
126
+ return new CloudflareTranscriptionAdapter(config, model);
127
+ }
128
+ function cloudflareTranscription(model, config) {
129
+ return new CloudflareTranscriptionAdapter(resolveConfigFromEnv(config), model);
130
+ }
131
+ //#endregion
132
+ export { CloudflareTranscriptionAdapter, cloudflareTranscription, createCloudflareTranscription };
133
+
134
+ //# sourceMappingURL=transcription.js.map