@tanstack/ai-cloudflare 0.0.0 → 0.1.1
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/LICENSE +21 -0
- package/README.md +53 -2
- package/dist/esm/adapters/embedding.d.ts +19 -0
- package/dist/esm/adapters/embedding.js +60 -0
- package/dist/esm/adapters/embedding.js.map +1 -0
- package/dist/esm/adapters/image.d.ts +26 -0
- package/dist/esm/adapters/image.js +62 -0
- package/dist/esm/adapters/image.js.map +1 -0
- package/dist/esm/adapters/summarize.d.ts +15 -0
- package/dist/esm/adapters/summarize.js +22 -0
- package/dist/esm/adapters/summarize.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +71 -0
- package/dist/esm/adapters/text.js +88 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/adapters/transcription.d.ts +21 -0
- package/dist/esm/adapters/transcription.js +134 -0
- package/dist/esm/adapters/transcription.js.map +1 -0
- package/dist/esm/adapters/tts.d.ts +26 -0
- package/dist/esm/adapters/tts.js +68 -0
- package/dist/esm/adapters/tts.js.map +1 -0
- package/dist/esm/byok.d.ts +9 -0
- package/dist/esm/byok.js +24 -0
- package/dist/esm/byok.js.map +1 -0
- package/dist/esm/gateway.d.ts +30 -0
- package/dist/esm/gateway.js +45 -0
- package/dist/esm/gateway.js.map +1 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/utils/config.d.ts +60 -0
- package/dist/esm/utils/config.js +48 -0
- package/dist/esm/utils/config.js.map +1 -0
- package/dist/esm/utils/fetch.d.ts +27 -0
- package/dist/esm/utils/fetch.js +94 -0
- package/dist/esm/utils/fetch.js.map +1 -0
- package/dist/esm/utils/models.d.ts +16 -0
- package/dist/esm/utils/run.d.ts +21 -0
- package/dist/esm/utils/run.js +62 -0
- package/dist/esm/utils/run.js.map +1 -0
- package/package.json +73 -4
- package/src/adapters/embedding.ts +84 -0
- package/src/adapters/image.ts +97 -0
- package/src/adapters/summarize.ts +46 -0
- package/src/adapters/text.ts +150 -0
- package/src/adapters/transcription.ts +227 -0
- package/src/adapters/tts.ts +100 -0
- package/src/byok.ts +21 -0
- package/src/gateway.ts +57 -0
- package/src/index.ts +68 -0
- package/src/utils/config.ts +129 -0
- package/src/utils/fetch.ts +131 -0
- package/src/utils/models.ts +41 -0
- package/src/utils/run.ts +116 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcription.js","names":[],"sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { isBindingConfig, resolveConfigFromEnv } from '../utils/config'\nimport { runModel } from '../utils/run'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n TranscriptionWord,\n} from '@tanstack/ai'\nimport type {\n CloudflareConfig,\n CloudflareConfigInput,\n FetchLike,\n} from '../utils/config'\nimport type { CloudflareTranscriptionModel } from '../utils/models'\n\n/** Extra inputs forwarded to the transcription model (model specific). */\nexport type CloudflareTranscriptionProviderOptions = Record<string, unknown>\n\ninterface WhisperOutput {\n text?: string\n transcription_info?: { language?: string; duration?: number }\n segments?: Array<{ start: number; end: number; text: string }>\n words?: Array<{ word?: string; start?: number; end?: number }>\n}\n\ninterface NovaOutput {\n results?: {\n channels?: Array<{\n alternatives?: Array<{\n transcript?: string\n words?: Array<{ word?: string; start?: number; end?: number }>\n }>\n }>\n }\n metadata?: { duration?: number }\n}\n\nasync function toBytes(\n audio: TranscriptionOptions['audio'],\n fetchImpl: FetchLike,\n signal: AbortSignal | undefined,\n): Promise<{ bytes: ArrayBuffer; contentType: string }> {\n if (typeof audio === 'string') {\n // Base64 (data URI or bare) or a URL to fetch.\n if (/^https?:\\/\\//.test(audio)) {\n const response = await fetchImpl(audio, { signal })\n if (!response.ok) {\n throw new Error(\n `Could not fetch audio from ${audio} (${response.status})`,\n )\n }\n return {\n bytes: await response.arrayBuffer(),\n contentType: response.headers.get('content-type') ?? 'audio/mpeg',\n }\n }\n const match = /^data:([^;]+);base64,(.*)$/.exec(audio)\n const base64 = match?.[2] ?? audio\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return { bytes: bytes.buffer, contentType: match?.[1] ?? 'audio/mpeg' }\n }\n if (audio instanceof ArrayBuffer) {\n return { bytes: audio, contentType: 'audio/mpeg' }\n }\n return {\n bytes: await audio.arrayBuffer(),\n contentType: audio.type || 'audio/mpeg',\n }\n}\n\n/**\n * Cloudflare transcription adapter. Whisper models take base64 audio in the\n * `audio` input; Deepgram Nova takes the raw bytes. Both return text plus\n * timed words, and Whisper also returns segments.\n */\nexport class CloudflareTranscriptionAdapter<\n TModel extends CloudflareTranscriptionModel,\n> extends BaseTranscriptionAdapter<\n TModel,\n CloudflareTranscriptionProviderOptions\n> {\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 transcribe(\n options: TranscriptionOptions<CloudflareTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, logger, language, prompt } = options\n try {\n logger.request(\n `activity=transcription provider=${this.name} model=${model}`,\n {\n provider: this.name,\n model,\n },\n )\n const fetchImpl =\n (isBindingConfig(this.cfConfig) ? undefined : this.cfConfig.fetch) ??\n fetch\n const { bytes, contentType } = await toBytes(\n options.audio,\n fetchImpl,\n options.abortSignal,\n )\n const output = model.startsWith('@cf/deepgram/')\n ? await this.runNova(model, bytes, contentType, options)\n : await this.runWhisper(\n model,\n bytes,\n { language, prompt, ...options.modelOptions },\n options.abortSignal,\n )\n return { id: generateId(this.name), model, ...output }\n } catch (error: unknown) {\n logger.errors(`${this.name}.transcribe fatal`, {\n error: toRunErrorPayload(error, `${this.name}.transcribe failed`),\n source: `${this.name}.transcribe`,\n })\n throw error\n }\n }\n\n private async runWhisper(\n model: string,\n bytes: ArrayBuffer,\n inputs: { language?: string; prompt?: string } & Record<string, unknown>,\n signal: AbortSignal | undefined,\n ): Promise<Omit<TranscriptionResult, 'id' | 'model'>> {\n const { language, prompt, ...rest } = inputs\n const output = (await runModel(\n this.cfConfig,\n model,\n {\n ...(language && { language }),\n ...(prompt && { initial_prompt: prompt }),\n ...rest,\n audio: arrayBufferToBase64(bytes),\n },\n { signal },\n )) as WhisperOutput\n if (typeof output.text !== 'string') {\n throw new Error(`Workers AI ${model} returned no transcript`)\n }\n return {\n text: output.text,\n language: output.transcription_info?.language,\n duration: output.transcription_info?.duration,\n segments: output.segments?.map(\n (segment, id): TranscriptionSegment => ({\n id,\n start: segment.start,\n end: segment.end,\n text: segment.text.trim(),\n }),\n ),\n words: toWords(output.words),\n }\n }\n\n private async runNova(\n model: string,\n bytes: ArrayBuffer,\n contentType: string,\n options: TranscriptionOptions<CloudflareTranscriptionProviderOptions>,\n ): Promise<Omit<TranscriptionResult, 'id' | 'model'>> {\n const output = (await runModel(\n this.cfConfig,\n model,\n {\n ...(options.language && { language: options.language }),\n ...options.modelOptions,\n },\n {\n signal: options.abortSignal,\n binary: { field: 'audio', body: bytes, contentType },\n },\n )) as NovaOutput\n const alternative = output.results?.channels?.[0]?.alternatives?.[0]\n if (typeof alternative?.transcript !== 'string') {\n throw new Error(`Workers AI ${model} returned no transcript`)\n }\n return {\n text: alternative.transcript,\n duration: output.metadata?.duration,\n words: toWords(alternative?.words),\n }\n }\n}\n\nfunction toWords(\n words: Array<{ word?: string; start?: number; end?: number }> | undefined,\n): Array<TranscriptionWord> | undefined {\n return words?.map((w) => ({\n word: (w.word ?? '').trim(),\n start: w.start ?? 0,\n end: w.end ?? 0,\n }))\n}\n\nexport function createCloudflareTranscription<\n TModel extends CloudflareTranscriptionModel,\n>(\n model: TModel,\n config: CloudflareConfig,\n): CloudflareTranscriptionAdapter<TModel> {\n return new CloudflareTranscriptionAdapter(config, model)\n}\n\nexport function cloudflareTranscription<\n TModel extends CloudflareTranscriptionModel,\n>(\n model: TModel,\n config?: CloudflareConfigInput,\n): CloudflareTranscriptionAdapter<TModel> {\n return new CloudflareTranscriptionAdapter(resolveConfigFromEnv(config), model)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BaseTTSAdapter } from '@tanstack/ai/adapters';
|
|
2
|
+
import { TTSOptions, TTSResult } from '@tanstack/ai';
|
|
3
|
+
import { CloudflareConfig, CloudflareConfigInput } from '../utils/config.js';
|
|
4
|
+
import { CloudflareTTSModel } from '../utils/models.js';
|
|
5
|
+
/** Text-to-speech inputs forwarded to the model (Deepgram Aura fields). */
|
|
6
|
+
export interface CloudflareTTSProviderOptions {
|
|
7
|
+
speaker?: string;
|
|
8
|
+
encoding?: 'linear16' | 'flac' | 'mulaw' | 'alaw' | 'mp3' | 'opus' | 'aac';
|
|
9
|
+
container?: 'none' | 'wav' | 'ogg';
|
|
10
|
+
sample_rate?: number;
|
|
11
|
+
bit_rate?: number;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Cloudflare text-to-speech adapter for Workers AI models such as Deepgram
|
|
16
|
+
* Aura. `voice` maps to `speaker` and `format` to `encoding`; the audio comes
|
|
17
|
+
* back base64-encoded.
|
|
18
|
+
*/
|
|
19
|
+
export declare class CloudflareTTSAdapter<TModel extends CloudflareTTSModel> extends BaseTTSAdapter<TModel, CloudflareTTSProviderOptions> {
|
|
20
|
+
private readonly cfConfig;
|
|
21
|
+
readonly name: "cloudflare";
|
|
22
|
+
constructor(cfConfig: CloudflareConfig, model: TModel);
|
|
23
|
+
generateSpeech(options: TTSOptions<CloudflareTTSProviderOptions>): Promise<TTSResult>;
|
|
24
|
+
}
|
|
25
|
+
export declare function createCloudflareTTS<TModel extends CloudflareTTSModel>(model: TModel, config: CloudflareConfig): CloudflareTTSAdapter<TModel>;
|
|
26
|
+
export declare function cloudflareTTS<TModel extends CloudflareTTSModel>(model: TModel, config?: CloudflareConfigInput): CloudflareTTSAdapter<TModel>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { resolveConfigFromEnv } from "../utils/config.js";
|
|
2
|
+
import { outputToBase64, runModel } from "../utils/run.js";
|
|
3
|
+
import { generateId } from "@tanstack/ai-utils";
|
|
4
|
+
import { BaseTTSAdapter } from "@tanstack/ai/adapters";
|
|
5
|
+
import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
|
|
6
|
+
//#region src/adapters/tts.ts
|
|
7
|
+
var CONTENT_TYPES = {
|
|
8
|
+
mp3: "audio/mpeg",
|
|
9
|
+
opus: "audio/opus",
|
|
10
|
+
aac: "audio/aac",
|
|
11
|
+
flac: "audio/flac",
|
|
12
|
+
wav: "audio/wav",
|
|
13
|
+
pcm: "audio/pcm"
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Cloudflare text-to-speech adapter for Workers AI models such as Deepgram
|
|
17
|
+
* Aura. `voice` maps to `speaker` and `format` to `encoding`; the audio comes
|
|
18
|
+
* back base64-encoded.
|
|
19
|
+
*/
|
|
20
|
+
var CloudflareTTSAdapter = class extends BaseTTSAdapter {
|
|
21
|
+
cfConfig;
|
|
22
|
+
name = "cloudflare";
|
|
23
|
+
constructor(cfConfig, model) {
|
|
24
|
+
super(model, {});
|
|
25
|
+
this.cfConfig = cfConfig;
|
|
26
|
+
}
|
|
27
|
+
async generateSpeech(options) {
|
|
28
|
+
const { model, logger, text, voice, format = "mp3" } = options;
|
|
29
|
+
const inputs = {
|
|
30
|
+
...voice && { speaker: voice },
|
|
31
|
+
encoding: format === "wav" || format === "pcm" ? "linear16" : format,
|
|
32
|
+
...format === "wav" && { container: "wav" },
|
|
33
|
+
...options.modelOptions,
|
|
34
|
+
text
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
logger.request(`activity=tts provider=${this.name} model=${model} chars=${text.length}`, {
|
|
38
|
+
provider: this.name,
|
|
39
|
+
model
|
|
40
|
+
});
|
|
41
|
+
const output = await runModel(this.cfConfig, model, inputs, { signal: options.abortSignal });
|
|
42
|
+
const audio = output && typeof output === "object" && "audio" in output ? output.audio : await outputToBase64(output);
|
|
43
|
+
return {
|
|
44
|
+
id: generateId(this.name),
|
|
45
|
+
model,
|
|
46
|
+
audio,
|
|
47
|
+
format,
|
|
48
|
+
contentType: CONTENT_TYPES[format]
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.errors(`${this.name}.generateSpeech fatal`, {
|
|
52
|
+
error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),
|
|
53
|
+
source: `${this.name}.generateSpeech`
|
|
54
|
+
});
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function createCloudflareTTS(model, config) {
|
|
60
|
+
return new CloudflareTTSAdapter(config, model);
|
|
61
|
+
}
|
|
62
|
+
function cloudflareTTS(model, config) {
|
|
63
|
+
return new CloudflareTTSAdapter(resolveConfigFromEnv(config), model);
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { CloudflareTTSAdapter, cloudflareTTS, createCloudflareTTS };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=tts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.js","names":[],"sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport { resolveConfigFromEnv } from '../utils/config'\nimport { outputToBase64, runModel } from '../utils/run'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type { CloudflareConfig, CloudflareConfigInput } from '../utils/config'\nimport type { CloudflareTTSModel } from '../utils/models'\n\n/** Text-to-speech inputs forwarded to the model (Deepgram Aura fields). */\nexport interface CloudflareTTSProviderOptions {\n speaker?: string\n encoding?: 'linear16' | 'flac' | 'mulaw' | 'alaw' | 'mp3' | 'opus' | 'aac'\n container?: 'none' | 'wav' | 'ogg'\n sample_rate?: number\n bit_rate?: number\n [key: string]: unknown\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n mp3: 'audio/mpeg',\n opus: 'audio/opus',\n aac: 'audio/aac',\n flac: 'audio/flac',\n wav: 'audio/wav',\n pcm: 'audio/pcm',\n}\n\n/**\n * Cloudflare text-to-speech adapter for Workers AI models such as Deepgram\n * Aura. `voice` maps to `speaker` and `format` to `encoding`; the audio comes\n * back base64-encoded.\n */\nexport class CloudflareTTSAdapter<\n TModel extends CloudflareTTSModel,\n> extends BaseTTSAdapter<TModel, CloudflareTTSProviderOptions> {\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 generateSpeech(\n options: TTSOptions<CloudflareTTSProviderOptions>,\n ): Promise<TTSResult> {\n const { model, logger, text, voice, format = 'mp3' } = options\n const inputs = {\n ...(voice && { speaker: voice }),\n // Aura takes the codec as `encoding` and the wrapper as `container`;\n // `wav` and `pcm` are both linear16.\n encoding: format === 'wav' || format === 'pcm' ? 'linear16' : format,\n ...(format === 'wav' && { container: 'wav' }),\n ...options.modelOptions,\n text,\n }\n try {\n logger.request(\n `activity=tts provider=${this.name} model=${model} chars=${text.length}`,\n { provider: this.name, model },\n )\n const output = await runModel(this.cfConfig, model, inputs, {\n signal: options.abortSignal,\n })\n const audio =\n output && typeof output === 'object' && 'audio' in output\n ? (output as { audio: string }).audio\n : await outputToBase64(output)\n return {\n id: generateId(this.name),\n model,\n audio,\n format,\n contentType: CONTENT_TYPES[format],\n }\n } catch (error: unknown) {\n logger.errors(`${this.name}.generateSpeech fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),\n source: `${this.name}.generateSpeech`,\n })\n throw error\n }\n }\n}\n\nexport function createCloudflareTTS<TModel extends CloudflareTTSModel>(\n model: TModel,\n config: CloudflareConfig,\n): CloudflareTTSAdapter<TModel> {\n return new CloudflareTTSAdapter(config, model)\n}\n\nexport function cloudflareTTS<TModel extends CloudflareTTSModel>(\n model: TModel,\n config?: CloudflareConfigInput,\n): CloudflareTTSAdapter<TModel> {\n return new CloudflareTTSAdapter(resolveConfigFromEnv(config), model)\n}\n"],"mappings":";;;;;;AAmBA,IAAM,gBAAwC;CAC5C,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;AACP;;;;;;AAOA,IAAa,uBAAb,cAEU,eAAqD;CAI1C;CAHnB,OAAgB;CAEhB,YACE,UACA,OACA;EACA,MAAM,OAAO,CAAC,CAAC;EAHE,KAAA,WAAA;CAInB;CAEA,MAAM,eACJ,SACoB;EACpB,MAAM,EAAE,OAAO,QAAQ,MAAM,OAAO,SAAS,UAAU;EACvD,MAAM,SAAS;GACb,GAAI,SAAS,EAAE,SAAS,MAAM;GAG9B,UAAU,WAAW,SAAS,WAAW,QAAQ,aAAa;GAC9D,GAAI,WAAW,SAAS,EAAE,WAAW,MAAM;GAC3C,GAAG,QAAQ;GACX;EACF;EACA,IAAI;GACF,OAAO,QACL,yBAAyB,KAAK,KAAK,SAAS,MAAM,SAAS,KAAK,UAChE;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GACA,MAAM,SAAS,MAAM,SAAS,KAAK,UAAU,OAAO,QAAQ,EAC1D,QAAQ,QAAQ,YAClB,CAAC;GACD,MAAM,QACJ,UAAU,OAAO,WAAW,YAAY,WAAW,SAC9C,OAA6B,QAC9B,MAAM,eAAe,MAAM;GACjC,OAAO;IACL,IAAI,WAAW,KAAK,IAAI;IACxB;IACA;IACA;IACA,aAAa,cAAc;GAC7B;EACF,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,oBACd,OACA,QAC8B;CAC9B,OAAO,IAAI,qBAAqB,QAAQ,KAAK;AAC/C;AAEA,SAAgB,cACd,OACA,QAC8B;CAC9B,OAAO,IAAI,qBAAqB,qBAAqB,MAAM,GAAG,KAAK;AACrE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The account id that goes with {@link cloudflareByok}. */
|
|
2
|
+
export declare const cloudflareAccountByok: import('@tanstack/ai/byok').ByokProvider<"cloudflare-account">;
|
|
3
|
+
/**
|
|
4
|
+
* BYOK descriptor for a user-supplied Cloudflare API token. A user who brings
|
|
5
|
+
* a token brings the account it belongs to, so {@link cloudflareAccountByok}
|
|
6
|
+
* rides along: register both with `defineByok({ providers })` and a send for
|
|
7
|
+
* `cloudflare` carries both headers.
|
|
8
|
+
*/
|
|
9
|
+
export declare const cloudflareByok: import('@tanstack/ai/byok').ByokProvider<"cloudflare">;
|
package/dist/esm/byok.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineByokProvider } from "@tanstack/ai/byok";
|
|
2
|
+
//#region src/byok.ts
|
|
3
|
+
/** The account id that goes with {@link cloudflareByok}. */
|
|
4
|
+
var cloudflareAccountByok = defineByokProvider({
|
|
5
|
+
id: "cloudflare-account",
|
|
6
|
+
label: "Cloudflare account ID",
|
|
7
|
+
env: "CLOUDFLARE_ACCOUNT_ID"
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* BYOK descriptor for a user-supplied Cloudflare API token. A user who brings
|
|
11
|
+
* a token brings the account it belongs to, so {@link cloudflareAccountByok}
|
|
12
|
+
* rides along: register both with `defineByok({ providers })` and a send for
|
|
13
|
+
* `cloudflare` carries both headers.
|
|
14
|
+
*/
|
|
15
|
+
var cloudflareByok = defineByokProvider({
|
|
16
|
+
id: "cloudflare",
|
|
17
|
+
label: "Cloudflare API token",
|
|
18
|
+
env: "CLOUDFLARE_API_TOKEN",
|
|
19
|
+
with: [cloudflareAccountByok]
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { cloudflareAccountByok, cloudflareByok };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=byok.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"byok.js","names":[],"sources":["../../src/byok.ts"],"sourcesContent":["import { defineByokProvider } from '@tanstack/ai/byok'\n\n/** The account id that goes with {@link cloudflareByok}. */\nexport const cloudflareAccountByok = defineByokProvider({\n id: 'cloudflare-account',\n label: 'Cloudflare account ID',\n env: 'CLOUDFLARE_ACCOUNT_ID',\n})\n\n/**\n * BYOK descriptor for a user-supplied Cloudflare API token. A user who brings\n * a token brings the account it belongs to, so {@link cloudflareAccountByok}\n * rides along: register both with `defineByok({ providers })` and a send for\n * `cloudflare` carries both headers.\n */\nexport const cloudflareByok = defineByokProvider({\n id: 'cloudflare',\n label: 'Cloudflare API token',\n env: 'CLOUDFLARE_API_TOKEN',\n with: [cloudflareAccountByok],\n})\n"],"mappings":";;;AAGA,IAAa,wBAAwB,mBAAmB;CACtD,IAAI;CACJ,OAAO;CACP,KAAK;AACP,CAAC;;;;;;;AAQD,IAAa,iBAAiB,mBAAmB;CAC/C,IAAI;CACJ,OAAO;CACP,KAAK;CACL,MAAM,CAAC,qBAAqB;AAC9B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AIGatewayProviders } from '@cloudflare/workers-types';
|
|
2
|
+
import { CloudflareGatewayOptions } from './utils/config.js';
|
|
3
|
+
export interface CloudflareGatewayTarget extends Omit<CloudflareGatewayOptions, 'id'> {
|
|
4
|
+
accountId: string;
|
|
5
|
+
gatewayId: string;
|
|
6
|
+
/** Cloudflare API token, needed when the gateway has authentication on. */
|
|
7
|
+
cfApiKey?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Builds the `baseURL` and headers that point any provider adapter at that
|
|
11
|
+
* provider's endpoint on your AI Gateway. Pass them through the adapter's
|
|
12
|
+
* client options (`baseURL` + `defaultHeaders` for OpenAI-style SDKs).
|
|
13
|
+
*
|
|
14
|
+
* The headers carry the per-request `cf-aig-*` options plus
|
|
15
|
+
* `cf-aig-authorization: Bearer <cfApiKey>` when `cfApiKey` is set. The
|
|
16
|
+
* gateway id lives in the URL, so no `cf-aig-gateway-id` header is sent.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const gateway = cloudflareGateway('openai', { accountId, gatewayId: 'prod' })
|
|
21
|
+
* const adapter = createOpenaiChat('gpt-5.5', process.env.OPENAI_API_KEY!, {
|
|
22
|
+
* baseURL: gateway.baseURL,
|
|
23
|
+
* defaultHeaders: gateway.headers,
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function cloudflareGateway(provider: AIGatewayProviders | 'compat' | (string & {}), target: CloudflareGatewayTarget): {
|
|
28
|
+
baseURL: string;
|
|
29
|
+
headers: Record<string, string>;
|
|
30
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { gatewayHeaders } from "./utils/config.js";
|
|
2
|
+
//#region src/gateway.ts
|
|
3
|
+
/**
|
|
4
|
+
* Gateway endpoints mirror each vendor's own path after the host, and most
|
|
5
|
+
* vendor SDKs already append their version segment (Anthropic `/v1/messages`,
|
|
6
|
+
* Mistral `/v1/...`, Cohere `/v1/chat`). OpenAI-style SDKs append only
|
|
7
|
+
* `/chat/completions`, which matches Cloudflare's `openai`, `groq`,
|
|
8
|
+
* `perplexity-ai`, `deepseek`, and `cerebras` endpoints. xAI is the exception:
|
|
9
|
+
* Cloudflare serves it at `/grok/v1/...`, so its base URL keeps the `/v1`.
|
|
10
|
+
*/
|
|
11
|
+
var PROVIDER_PATH_SUFFIX = { grok: "/v1" };
|
|
12
|
+
/**
|
|
13
|
+
* Builds the `baseURL` and headers that point any provider adapter at that
|
|
14
|
+
* provider's endpoint on your AI Gateway. Pass them through the adapter's
|
|
15
|
+
* client options (`baseURL` + `defaultHeaders` for OpenAI-style SDKs).
|
|
16
|
+
*
|
|
17
|
+
* The headers carry the per-request `cf-aig-*` options plus
|
|
18
|
+
* `cf-aig-authorization: Bearer <cfApiKey>` when `cfApiKey` is set. The
|
|
19
|
+
* gateway id lives in the URL, so no `cf-aig-gateway-id` header is sent.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const gateway = cloudflareGateway('openai', { accountId, gatewayId: 'prod' })
|
|
24
|
+
* const adapter = createOpenaiChat('gpt-5.5', process.env.OPENAI_API_KEY!, {
|
|
25
|
+
* baseURL: gateway.baseURL,
|
|
26
|
+
* defaultHeaders: gateway.headers,
|
|
27
|
+
* })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function cloudflareGateway(provider, target) {
|
|
31
|
+
const { accountId, gatewayId, cfApiKey, ...options } = target;
|
|
32
|
+
const { "cf-aig-gateway-id": _id, ...headers } = gatewayHeaders({
|
|
33
|
+
id: gatewayId,
|
|
34
|
+
...options
|
|
35
|
+
});
|
|
36
|
+
if (cfApiKey) headers["cf-aig-authorization"] = `Bearer ${cfApiKey}`;
|
|
37
|
+
return {
|
|
38
|
+
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/${provider}${PROVIDER_PATH_SUFFIX[provider] ?? ""}`,
|
|
39
|
+
headers
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { cloudflareGateway };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","names":[],"sources":["../../src/gateway.ts"],"sourcesContent":["import { gatewayHeaders } from './utils/config'\nimport type { AIGatewayProviders } from '@cloudflare/workers-types'\nimport type { CloudflareGatewayOptions } from './utils/config'\n\nexport interface CloudflareGatewayTarget extends Omit<\n CloudflareGatewayOptions,\n 'id'\n> {\n accountId: string\n gatewayId: string\n /** Cloudflare API token, needed when the gateway has authentication on. */\n cfApiKey?: string\n}\n\n/**\n * Gateway endpoints mirror each vendor's own path after the host, and most\n * vendor SDKs already append their version segment (Anthropic `/v1/messages`,\n * Mistral `/v1/...`, Cohere `/v1/chat`). OpenAI-style SDKs append only\n * `/chat/completions`, which matches Cloudflare's `openai`, `groq`,\n * `perplexity-ai`, `deepseek`, and `cerebras` endpoints. xAI is the exception:\n * Cloudflare serves it at `/grok/v1/...`, so its base URL keeps the `/v1`.\n */\nconst PROVIDER_PATH_SUFFIX: Record<string, string> = { grok: '/v1' }\n\n/**\n * Builds the `baseURL` and headers that point any provider adapter at that\n * provider's endpoint on your AI Gateway. Pass them through the adapter's\n * client options (`baseURL` + `defaultHeaders` for OpenAI-style SDKs).\n *\n * The headers carry the per-request `cf-aig-*` options plus\n * `cf-aig-authorization: Bearer <cfApiKey>` when `cfApiKey` is set. The\n * gateway id lives in the URL, so no `cf-aig-gateway-id` header is sent.\n *\n * @example\n * ```typescript\n * const gateway = cloudflareGateway('openai', { accountId, gatewayId: 'prod' })\n * const adapter = createOpenaiChat('gpt-5.5', process.env.OPENAI_API_KEY!, {\n * baseURL: gateway.baseURL,\n * defaultHeaders: gateway.headers,\n * })\n * ```\n */\nexport function cloudflareGateway(\n provider: AIGatewayProviders | 'compat' | (string & {}),\n target: CloudflareGatewayTarget,\n): { baseURL: string; headers: Record<string, string> } {\n const { accountId, gatewayId, cfApiKey, ...options } = target\n const { 'cf-aig-gateway-id': _id, ...headers } = gatewayHeaders({\n id: gatewayId,\n ...options,\n })\n if (cfApiKey) headers['cf-aig-authorization'] = `Bearer ${cfApiKey}`\n return {\n baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/${provider}${PROVIDER_PATH_SUFFIX[provider] ?? ''}`,\n headers,\n }\n}\n"],"mappings":";;;;;;;;;;AAsBA,IAAM,uBAA+C,EAAE,MAAM,MAAM;;;;;;;;;;;;;;;;;;;AAoBnE,SAAgB,kBACd,UACA,QACsD;CACtD,MAAM,EAAE,WAAW,WAAW,UAAU,GAAG,YAAY;CACvD,MAAM,EAAE,qBAAqB,KAAK,GAAG,YAAY,eAAe;EAC9D,IAAI;EACJ,GAAG;CACL,CAAC;CACD,IAAI,UAAU,QAAQ,0BAA0B,UAAU;CAC1D,OAAO;EACL,SAAS,wCAAwC,UAAU,GAAG,UAAU,GAAG,WAAW,qBAAqB,aAAa;EACxH;CACF;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @tanstack/ai-cloudflare
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare provider adapter for TanStack AI: Workers AI chat, embeddings,
|
|
5
|
+
* images, speech and transcription over the `env.AI` binding or the REST
|
|
6
|
+
* API, plus AI Gateway routing for any provider.
|
|
7
|
+
*/
|
|
8
|
+
export { CloudflareTextAdapter, createCloudflareText, cloudflareText, type CloudflareTextProviderOptions, } from './adapters/text.js';
|
|
9
|
+
export { createCloudflareSummarize, cloudflareSummarize, type CloudflareSummarizeModel, } from './adapters/summarize.js';
|
|
10
|
+
export { CloudflareEmbeddingAdapter, createCloudflareEmbedding, cloudflareEmbedding, type CloudflareEmbeddingProviderOptions, } from './adapters/embedding.js';
|
|
11
|
+
export { CloudflareImageAdapter, createCloudflareImage, cloudflareImage, type CloudflareImageProviderOptions, } from './adapters/image.js';
|
|
12
|
+
export { CloudflareTTSAdapter, createCloudflareTTS, cloudflareTTS, type CloudflareTTSProviderOptions, } from './adapters/tts.js';
|
|
13
|
+
export { CloudflareTranscriptionAdapter, createCloudflareTranscription, cloudflareTranscription, type CloudflareTranscriptionProviderOptions, } from './adapters/transcription.js';
|
|
14
|
+
export { cloudflareGateway, type CloudflareGatewayTarget } from './gateway.js';
|
|
15
|
+
export type { CloudflareBindingConfig, CloudflareConfig, CloudflareConfigInput, CloudflareGatewayOptions, CloudflareRestConfig, CloudflareTextConfig, CloudflareTextRestConfig, } from './utils/config.js';
|
|
16
|
+
export type { CloudflareEmbeddingModel, CloudflareImageModel, CloudflareTextModel, CloudflareTranscriptionModel, CloudflareTTSModel, } from './utils/models.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CloudflareTextAdapter, cloudflareText, createCloudflareText } from "./adapters/text.js";
|
|
2
|
+
import { cloudflareSummarize, createCloudflareSummarize } from "./adapters/summarize.js";
|
|
3
|
+
import { CloudflareEmbeddingAdapter, cloudflareEmbedding, createCloudflareEmbedding } from "./adapters/embedding.js";
|
|
4
|
+
import { CloudflareImageAdapter, cloudflareImage, createCloudflareImage } from "./adapters/image.js";
|
|
5
|
+
import { CloudflareTTSAdapter, cloudflareTTS, createCloudflareTTS } from "./adapters/tts.js";
|
|
6
|
+
import { CloudflareTranscriptionAdapter, cloudflareTranscription, createCloudflareTranscription } from "./adapters/transcription.js";
|
|
7
|
+
import { cloudflareGateway } from "./gateway.js";
|
|
8
|
+
export { CloudflareEmbeddingAdapter, CloudflareImageAdapter, CloudflareTTSAdapter, CloudflareTextAdapter, CloudflareTranscriptionAdapter, cloudflareEmbedding, cloudflareGateway, cloudflareImage, cloudflareSummarize, cloudflareTTS, cloudflareText, cloudflareTranscription, createCloudflareEmbedding, createCloudflareImage, createCloudflareSummarize, createCloudflareTTS, createCloudflareText, createCloudflareTranscription };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Ai, GatewayOptions } from '@cloudflare/workers-types';
|
|
2
|
+
import { ClientOptions } from 'openai';
|
|
3
|
+
/**
|
|
4
|
+
* AI Gateway routing options. `id` is the gateway id (use `"default"` for the
|
|
5
|
+
* account's auto-created gateway). The remaining fields are per-request
|
|
6
|
+
* gateway controls (cache, logging, retries) and map to `cf-aig-*` headers on
|
|
7
|
+
* the REST path or to the `gateway` run option on the binding path.
|
|
8
|
+
*/
|
|
9
|
+
export type CloudflareGatewayOptions = GatewayOptions;
|
|
10
|
+
export type FetchLike = NonNullable<ClientOptions['fetch']>;
|
|
11
|
+
/**
|
|
12
|
+
* Run through the Workers AI binding (`env.AI`) inside a Cloudflare Worker.
|
|
13
|
+
* No API token is needed.
|
|
14
|
+
*/
|
|
15
|
+
export interface CloudflareBindingConfig {
|
|
16
|
+
binding: Ai;
|
|
17
|
+
gateway?: CloudflareGatewayOptions;
|
|
18
|
+
accountId?: never;
|
|
19
|
+
apiKey?: never;
|
|
20
|
+
}
|
|
21
|
+
/** Run through the Cloudflare REST API from any runtime. */
|
|
22
|
+
export interface CloudflareRestConfig {
|
|
23
|
+
accountId: string;
|
|
24
|
+
apiKey: string;
|
|
25
|
+
gateway?: CloudflareGatewayOptions;
|
|
26
|
+
/** Custom fetch for every request. */
|
|
27
|
+
fetch?: FetchLike;
|
|
28
|
+
binding?: never;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* REST config for the chat surface. Also accepts the OpenAI SDK client
|
|
32
|
+
* options (`baseURL`, `defaultHeaders`, `timeout`, `maxRetries`, ...), which
|
|
33
|
+
* only the text and summarize adapters read.
|
|
34
|
+
*/
|
|
35
|
+
export interface CloudflareTextRestConfig extends CloudflareRestConfig, Omit<ClientOptions, 'apiKey' | 'fetch'> {
|
|
36
|
+
}
|
|
37
|
+
export type CloudflareConfig = CloudflareBindingConfig | CloudflareRestConfig;
|
|
38
|
+
export type CloudflareTextConfig = CloudflareBindingConfig | CloudflareTextRestConfig;
|
|
39
|
+
/**
|
|
40
|
+
* What the env-reading factories accept: a binding, or REST fields with any
|
|
41
|
+
* missing ones read from `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`.
|
|
42
|
+
*/
|
|
43
|
+
export type CloudflareConfigInput<TRest extends CloudflareRestConfig = CloudflareRestConfig> = CloudflareBindingConfig | Partial<TRest>;
|
|
44
|
+
export declare const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
45
|
+
export declare function isBindingConfig(config: {
|
|
46
|
+
binding?: Ai;
|
|
47
|
+
}): config is CloudflareBindingConfig;
|
|
48
|
+
/** Base URL for the OpenAI-compatible chat surface of an account. */
|
|
49
|
+
export declare function restChatBaseURL(config: CloudflareTextRestConfig): string;
|
|
50
|
+
/**
|
|
51
|
+
* Translates gateway options into the `cf-aig-*` request headers the REST
|
|
52
|
+
* API reads. Retries are not mapped: set them on the gateway itself.
|
|
53
|
+
*/
|
|
54
|
+
export declare function gatewayHeaders(gateway: CloudflareGatewayOptions | undefined): Record<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Resolves a config for the env-reading factories: a binding config passes
|
|
57
|
+
* through, anything else is filled from `CLOUDFLARE_ACCOUNT_ID` and
|
|
58
|
+
* `CLOUDFLARE_API_TOKEN`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveConfigFromEnv<TRest extends CloudflareRestConfig>(config: CloudflareConfigInput<TRest> | undefined): CloudflareBindingConfig | (Partial<TRest> & CloudflareRestConfig);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getApiKeyFromEnv } from "@tanstack/ai-utils";
|
|
2
|
+
//#region src/utils/config.ts
|
|
3
|
+
var CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
4
|
+
function isBindingConfig(config) {
|
|
5
|
+
return config.binding !== void 0;
|
|
6
|
+
}
|
|
7
|
+
/** Base URL for the OpenAI-compatible chat surface of an account. */
|
|
8
|
+
function restChatBaseURL(config) {
|
|
9
|
+
return config.baseURL || `https://api.cloudflare.com/client/v4/accounts/${config.accountId}/ai/v1`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Translates gateway options into the `cf-aig-*` request headers the REST
|
|
13
|
+
* API reads. Retries are not mapped: set them on the gateway itself.
|
|
14
|
+
*/
|
|
15
|
+
function gatewayHeaders(gateway) {
|
|
16
|
+
if (!gateway) return {};
|
|
17
|
+
const headers = { "cf-aig-gateway-id": gateway.id };
|
|
18
|
+
if (gateway.skipCache !== void 0) headers["cf-aig-skip-cache"] = String(gateway.skipCache);
|
|
19
|
+
if (gateway.cacheTtl !== void 0) headers["cf-aig-cache-ttl"] = String(gateway.cacheTtl);
|
|
20
|
+
if (gateway.cacheKey !== void 0) headers["cf-aig-cache-key"] = gateway.cacheKey;
|
|
21
|
+
if (gateway.collectLog !== void 0) headers["cf-aig-collect-log"] = String(gateway.collectLog);
|
|
22
|
+
if (gateway.eventId !== void 0) headers["cf-aig-event-id"] = gateway.eventId;
|
|
23
|
+
if (gateway.requestTimeoutMs !== void 0) headers["cf-aig-request-timeout"] = String(gateway.requestTimeoutMs);
|
|
24
|
+
if (gateway.metadata !== void 0) headers["cf-aig-metadata"] = JSON.stringify(gateway.metadata);
|
|
25
|
+
return headers;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolves a config for the env-reading factories: a binding config passes
|
|
29
|
+
* through, anything else is filled from `CLOUDFLARE_ACCOUNT_ID` and
|
|
30
|
+
* `CLOUDFLARE_API_TOKEN`.
|
|
31
|
+
*/
|
|
32
|
+
function resolveConfigFromEnv(config) {
|
|
33
|
+
if (config && isBindingConfig(config)) return config;
|
|
34
|
+
const rest = config ?? {};
|
|
35
|
+
try {
|
|
36
|
+
return {
|
|
37
|
+
...rest,
|
|
38
|
+
accountId: rest.accountId ?? getApiKeyFromEnv("CLOUDFLARE_ACCOUNT_ID"),
|
|
39
|
+
apiKey: rest.apiKey ?? getApiKeyFromEnv("CLOUDFLARE_API_TOKEN")
|
|
40
|
+
};
|
|
41
|
+
} catch (cause) {
|
|
42
|
+
throw new Error("CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN are required. Set them in your environment, pass { accountId, apiKey }, or pass { binding: env.AI } inside a Worker.", { cause });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { CLOUDFLARE_API_BASE, gatewayHeaders, isBindingConfig, resolveConfigFromEnv, restChatBaseURL };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../../../src/utils/config.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { Ai, GatewayOptions } from '@cloudflare/workers-types'\nimport type { ClientOptions } from 'openai'\n\n/**\n * AI Gateway routing options. `id` is the gateway id (use `\"default\"` for the\n * account's auto-created gateway). The remaining fields are per-request\n * gateway controls (cache, logging, retries) and map to `cf-aig-*` headers on\n * the REST path or to the `gateway` run option on the binding path.\n */\nexport type CloudflareGatewayOptions = GatewayOptions\n\nexport type FetchLike = NonNullable<ClientOptions['fetch']>\n\n/**\n * Run through the Workers AI binding (`env.AI`) inside a Cloudflare Worker.\n * No API token is needed.\n */\nexport interface CloudflareBindingConfig {\n binding: Ai\n gateway?: CloudflareGatewayOptions\n accountId?: never\n apiKey?: never\n}\n\n/** Run through the Cloudflare REST API from any runtime. */\nexport interface CloudflareRestConfig {\n accountId: string\n apiKey: string\n gateway?: CloudflareGatewayOptions\n /** Custom fetch for every request. */\n fetch?: FetchLike\n binding?: never\n}\n\n/**\n * REST config for the chat surface. Also accepts the OpenAI SDK client\n * options (`baseURL`, `defaultHeaders`, `timeout`, `maxRetries`, ...), which\n * only the text and summarize adapters read.\n */\nexport interface CloudflareTextRestConfig\n extends CloudflareRestConfig, Omit<ClientOptions, 'apiKey' | 'fetch'> {}\n\nexport type CloudflareConfig = CloudflareBindingConfig | CloudflareRestConfig\n\nexport type CloudflareTextConfig =\n | CloudflareBindingConfig\n | CloudflareTextRestConfig\n\n/**\n * What the env-reading factories accept: a binding, or REST fields with any\n * missing ones read from `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`.\n */\nexport type CloudflareConfigInput<\n TRest extends CloudflareRestConfig = CloudflareRestConfig,\n> = CloudflareBindingConfig | Partial<TRest>\n\nexport const CLOUDFLARE_API_BASE = 'https://api.cloudflare.com/client/v4'\n\nexport function isBindingConfig(config: {\n binding?: Ai\n}): config is CloudflareBindingConfig {\n return config.binding !== undefined\n}\n\n/** Base URL for the OpenAI-compatible chat surface of an account. */\nexport function restChatBaseURL(config: CloudflareTextRestConfig): string {\n return (\n config.baseURL ||\n `${CLOUDFLARE_API_BASE}/accounts/${config.accountId}/ai/v1`\n )\n}\n\n/**\n * Translates gateway options into the `cf-aig-*` request headers the REST\n * API reads. Retries are not mapped: set them on the gateway itself.\n */\nexport function gatewayHeaders(\n gateway: CloudflareGatewayOptions | undefined,\n): Record<string, string> {\n if (!gateway) return {}\n const headers: Record<string, string> = { 'cf-aig-gateway-id': gateway.id }\n if (gateway.skipCache !== undefined) {\n headers['cf-aig-skip-cache'] = String(gateway.skipCache)\n }\n if (gateway.cacheTtl !== undefined) {\n headers['cf-aig-cache-ttl'] = String(gateway.cacheTtl)\n }\n if (gateway.cacheKey !== undefined) {\n headers['cf-aig-cache-key'] = gateway.cacheKey\n }\n if (gateway.collectLog !== undefined) {\n headers['cf-aig-collect-log'] = String(gateway.collectLog)\n }\n if (gateway.eventId !== undefined) {\n headers['cf-aig-event-id'] = gateway.eventId\n }\n if (gateway.requestTimeoutMs !== undefined) {\n headers['cf-aig-request-timeout'] = String(gateway.requestTimeoutMs)\n }\n if (gateway.metadata !== undefined) {\n headers['cf-aig-metadata'] = JSON.stringify(gateway.metadata)\n }\n return headers\n}\n\n/**\n * Resolves a config for the env-reading factories: a binding config passes\n * through, anything else is filled from `CLOUDFLARE_ACCOUNT_ID` and\n * `CLOUDFLARE_API_TOKEN`.\n */\nexport function resolveConfigFromEnv<TRest extends CloudflareRestConfig>(\n config: CloudflareConfigInput<TRest> | undefined,\n): CloudflareBindingConfig | (Partial<TRest> & CloudflareRestConfig) {\n if (config && isBindingConfig(config)) return config\n const rest: Partial<TRest> = config ?? {}\n try {\n return {\n ...rest,\n accountId: rest.accountId ?? getApiKeyFromEnv('CLOUDFLARE_ACCOUNT_ID'),\n apiKey: rest.apiKey ?? getApiKeyFromEnv('CLOUDFLARE_API_TOKEN'),\n }\n } catch (cause) {\n throw new Error(\n 'CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN are required. Set them in your environment, pass { accountId, apiKey }, or pass { binding: env.AI } inside a Worker.',\n { cause },\n )\n }\n}\n"],"mappings":";;AAyDA,IAAa,sBAAsB;AAEnC,SAAgB,gBAAgB,QAEM;CACpC,OAAO,OAAO,YAAY,KAAA;AAC5B;;AAGA,SAAgB,gBAAgB,QAA0C;CACxE,OACE,OAAO,WACP,iDAAmC,OAAO,UAAU;AAExD;;;;;AAMA,SAAgB,eACd,SACwB;CACxB,IAAI,CAAC,SAAS,OAAO,CAAC;CACtB,MAAM,UAAkC,EAAE,qBAAqB,QAAQ,GAAG;CAC1E,IAAI,QAAQ,cAAc,KAAA,GACxB,QAAQ,uBAAuB,OAAO,QAAQ,SAAS;CAEzD,IAAI,QAAQ,aAAa,KAAA,GACvB,QAAQ,sBAAsB,OAAO,QAAQ,QAAQ;CAEvD,IAAI,QAAQ,aAAa,KAAA,GACvB,QAAQ,sBAAsB,QAAQ;CAExC,IAAI,QAAQ,eAAe,KAAA,GACzB,QAAQ,wBAAwB,OAAO,QAAQ,UAAU;CAE3D,IAAI,QAAQ,YAAY,KAAA,GACtB,QAAQ,qBAAqB,QAAQ;CAEvC,IAAI,QAAQ,qBAAqB,KAAA,GAC/B,QAAQ,4BAA4B,OAAO,QAAQ,gBAAgB;CAErE,IAAI,QAAQ,aAAa,KAAA,GACvB,QAAQ,qBAAqB,KAAK,UAAU,QAAQ,QAAQ;CAE9D,OAAO;AACT;;;;;;AAOA,SAAgB,qBACd,QACmE;CACnE,IAAI,UAAU,gBAAgB,MAAM,GAAG,OAAO;CAC9C,MAAM,OAAuB,UAAU,CAAC;CACxC,IAAI;EACF,OAAO;GACL,GAAG;GACH,WAAW,KAAK,aAAa,iBAAiB,uBAAuB;GACrE,QAAQ,KAAK,UAAU,iBAAiB,sBAAsB;EAChE;CACF,SAAS,OAAO;EACd,MAAM,IAAI,MACR,uKACA,EAAE,MAAM,CACV;CACF;AACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Ai } from '@cloudflare/workers-types';
|
|
2
|
+
import { CloudflareGatewayOptions, FetchLike } from './config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Workers AI streams end with a usage-only trailer (`{"response":"","usage":
|
|
5
|
+
* {...}}`) that has no `choices` field. The OpenAI Chat Completions stream
|
|
6
|
+
* reader indexes `chunk.choices[0]` on every event, so give such events an
|
|
7
|
+
* empty `choices` array (OpenAI's own usage-only trailer shape) and keep the
|
|
8
|
+
* usage totals they carry.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeSseResponse(response: Response): Response;
|
|
11
|
+
/**
|
|
12
|
+
* Cloudflare error bodies look like `{ name, message, internalCode }` or
|
|
13
|
+
* `{ errors: [{ code, message }] }`. The OpenAI SDK only reads
|
|
14
|
+
* `body.error.message`, so rewrap them or every failure reads as
|
|
15
|
+
* "status code (no body)".
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeErrorResponse(response: Response): Promise<Response>;
|
|
18
|
+
/** Applies the error and SSE normalizations a raw Cloudflare response needs. */
|
|
19
|
+
export declare function normalizeResponse(response: Response): Promise<Response>;
|
|
20
|
+
/**
|
|
21
|
+
* Makes `env.AI` look like an OpenAI-compatible HTTP endpoint to the OpenAI
|
|
22
|
+
* SDK: the JSON request body becomes `binding.run(model, inputs)` and the
|
|
23
|
+
* raw inference `Response` (OpenAI-format JSON or SSE) is handed back.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createBindingFetch(binding: Ai, gateway?: CloudflareGatewayOptions): FetchLike;
|
|
26
|
+
/** Wraps a REST fetch so responses get the same error and trailer fixes. */
|
|
27
|
+
export declare function createRestFetch(baseFetch: FetchLike | undefined): FetchLike;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region src/utils/fetch.ts
|
|
2
|
+
/**
|
|
3
|
+
* Workers AI streams end with a usage-only trailer (`{"response":"","usage":
|
|
4
|
+
* {...}}`) that has no `choices` field. The OpenAI Chat Completions stream
|
|
5
|
+
* reader indexes `chunk.choices[0]` on every event, so give such events an
|
|
6
|
+
* empty `choices` array (OpenAI's own usage-only trailer shape) and keep the
|
|
7
|
+
* usage totals they carry.
|
|
8
|
+
*/
|
|
9
|
+
function normalizeSseResponse(response) {
|
|
10
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
11
|
+
if (!response.body || !contentType.includes("text/event-stream")) return response;
|
|
12
|
+
let buffer = "";
|
|
13
|
+
const fixLine = (line) => {
|
|
14
|
+
if (!line.startsWith("data: ") || line === "data: [DONE]") return line;
|
|
15
|
+
try {
|
|
16
|
+
const event = JSON.parse(line.slice(6));
|
|
17
|
+
if (event && typeof event === "object" && !("choices" in event)) return `data: ${JSON.stringify({
|
|
18
|
+
...event,
|
|
19
|
+
choices: []
|
|
20
|
+
})}`;
|
|
21
|
+
} catch {}
|
|
22
|
+
return line;
|
|
23
|
+
};
|
|
24
|
+
const body = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new TransformStream({
|
|
25
|
+
transform(chunk, controller) {
|
|
26
|
+
buffer += chunk;
|
|
27
|
+
const lines = buffer.split("\n");
|
|
28
|
+
buffer = lines.pop() ?? "";
|
|
29
|
+
for (const line of lines) controller.enqueue(`${fixLine(line)}\n`);
|
|
30
|
+
},
|
|
31
|
+
flush(controller) {
|
|
32
|
+
if (buffer) controller.enqueue(fixLine(buffer));
|
|
33
|
+
}
|
|
34
|
+
})).pipeThrough(new TextEncoderStream());
|
|
35
|
+
return new Response(body, {
|
|
36
|
+
status: response.status,
|
|
37
|
+
statusText: response.statusText,
|
|
38
|
+
headers: response.headers
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Cloudflare error bodies look like `{ name, message, internalCode }` or
|
|
43
|
+
* `{ errors: [{ code, message }] }`. The OpenAI SDK only reads
|
|
44
|
+
* `body.error.message`, so rewrap them or every failure reads as
|
|
45
|
+
* "status code (no body)".
|
|
46
|
+
*/
|
|
47
|
+
async function normalizeErrorResponse(response) {
|
|
48
|
+
const text = await response.text();
|
|
49
|
+
let body = text;
|
|
50
|
+
try {
|
|
51
|
+
const json = JSON.parse(text);
|
|
52
|
+
if (json && typeof json === "object" && !("error" in json)) {
|
|
53
|
+
const first = json.errors?.[0];
|
|
54
|
+
body = JSON.stringify({ error: {
|
|
55
|
+
message: json.message ?? first?.message ?? text,
|
|
56
|
+
type: json.name ?? "cloudflare_error",
|
|
57
|
+
code: json.internalCode ?? first?.code ?? null
|
|
58
|
+
} });
|
|
59
|
+
}
|
|
60
|
+
} catch {}
|
|
61
|
+
return new Response(body, {
|
|
62
|
+
status: response.status,
|
|
63
|
+
statusText: response.statusText,
|
|
64
|
+
headers: response.headers
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Applies the error and SSE normalizations a raw Cloudflare response needs. */
|
|
68
|
+
async function normalizeResponse(response) {
|
|
69
|
+
return response.ok ? normalizeSseResponse(response) : await normalizeErrorResponse(response);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Makes `env.AI` look like an OpenAI-compatible HTTP endpoint to the OpenAI
|
|
73
|
+
* SDK: the JSON request body becomes `binding.run(model, inputs)` and the
|
|
74
|
+
* raw inference `Response` (OpenAI-format JSON or SSE) is handed back.
|
|
75
|
+
*/
|
|
76
|
+
function createBindingFetch(binding, gateway) {
|
|
77
|
+
const run = binding.run.bind(binding);
|
|
78
|
+
return async (_input, init) => {
|
|
79
|
+
const { model, ...inputs } = JSON.parse(typeof init?.body === "string" ? init.body : "{}");
|
|
80
|
+
return await normalizeResponse(await run(model, inputs, {
|
|
81
|
+
returnRawResponse: true,
|
|
82
|
+
...gateway && { gateway }
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Wraps a REST fetch so responses get the same error and trailer fixes. */
|
|
87
|
+
function createRestFetch(baseFetch) {
|
|
88
|
+
const fetchImpl = baseFetch ?? fetch;
|
|
89
|
+
return async (input, init) => await normalizeResponse(await fetchImpl(input, init));
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { createBindingFetch, createRestFetch, normalizeErrorResponse, normalizeResponse, normalizeSseResponse };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","names":[],"sources":["../../../src/utils/fetch.ts"],"sourcesContent":["import type { Ai } from '@cloudflare/workers-types'\nimport type { CloudflareGatewayOptions, FetchLike } from './config'\n\n/**\n * Workers AI streams end with a usage-only trailer (`{\"response\":\"\",\"usage\":\n * {...}}`) that has no `choices` field. The OpenAI Chat Completions stream\n * reader indexes `chunk.choices[0]` on every event, so give such events an\n * empty `choices` array (OpenAI's own usage-only trailer shape) and keep the\n * usage totals they carry.\n */\nexport function normalizeSseResponse(response: Response): Response {\n const contentType = response.headers.get('content-type') ?? ''\n if (!response.body || !contentType.includes('text/event-stream')) {\n return response\n }\n let buffer = ''\n const fixLine = (line: string): string => {\n if (!line.startsWith('data: ') || line === 'data: [DONE]') return line\n try {\n const event = JSON.parse(line.slice(6)) as Record<string, unknown>\n if (event && typeof event === 'object' && !('choices' in event)) {\n return `data: ${JSON.stringify({ ...event, choices: [] })}`\n }\n } catch {\n // Not JSON: forward untouched.\n }\n return line\n }\n const body = response.body\n .pipeThrough(new TextDecoderStream())\n .pipeThrough(\n new TransformStream<string, string>({\n transform(chunk, controller) {\n buffer += chunk\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) controller.enqueue(`${fixLine(line)}\\n`)\n },\n flush(controller) {\n if (buffer) controller.enqueue(fixLine(buffer))\n },\n }),\n )\n .pipeThrough(new TextEncoderStream())\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n}\n\n/**\n * Cloudflare error bodies look like `{ name, message, internalCode }` or\n * `{ errors: [{ code, message }] }`. The OpenAI SDK only reads\n * `body.error.message`, so rewrap them or every failure reads as\n * \"status code (no body)\".\n */\nexport async function normalizeErrorResponse(\n response: Response,\n): Promise<Response> {\n const text = await response.text()\n let body = text\n try {\n const json = JSON.parse(text) as {\n error?: unknown\n errors?: Array<{ code?: number; message?: string }>\n message?: string\n name?: string\n internalCode?: number\n }\n if (json && typeof json === 'object' && !('error' in json)) {\n const first = json.errors?.[0]\n body = JSON.stringify({\n error: {\n message: json.message ?? first?.message ?? text,\n type: json.name ?? 'cloudflare_error',\n code: json.internalCode ?? first?.code ?? null,\n },\n })\n }\n } catch {\n // Not JSON: forward the text as-is.\n }\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n}\n\n/** Applies the error and SSE normalizations a raw Cloudflare response needs. */\nexport async function normalizeResponse(response: Response): Promise<Response> {\n return response.ok\n ? normalizeSseResponse(response)\n : await normalizeErrorResponse(response)\n}\n\n/**\n * Makes `env.AI` look like an OpenAI-compatible HTTP endpoint to the OpenAI\n * SDK: the JSON request body becomes `binding.run(model, inputs)` and the\n * raw inference `Response` (OpenAI-format JSON or SSE) is handed back.\n */\nexport function createBindingFetch(\n binding: Ai,\n gateway?: CloudflareGatewayOptions,\n): FetchLike {\n // `Ai` is typed against the bundled model catalog; the adapter accepts any\n // model id, so widen the binding to the open catalog shape for this call.\n const run = binding.run.bind(binding) as (\n model: string,\n inputs: Record<string, unknown>,\n options: Record<string, unknown>,\n ) => Promise<unknown>\n return async (_input, init) => {\n const { model, ...inputs } = JSON.parse(\n typeof init?.body === 'string' ? init.body : '{}',\n ) as { model: string } & Record<string, unknown>\n const response = (await run(model, inputs, {\n returnRawResponse: true,\n ...(gateway && { gateway }),\n })) as Response\n return await normalizeResponse(response)\n }\n}\n\n/** Wraps a REST fetch so responses get the same error and trailer fixes. */\nexport function createRestFetch(baseFetch: FetchLike | undefined): FetchLike {\n const fetchImpl = baseFetch ?? fetch\n return async (input, init) =>\n await normalizeResponse(await fetchImpl(input, init))\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,qBAAqB,UAA8B;CACjE,MAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;CAC5D,IAAI,CAAC,SAAS,QAAQ,CAAC,YAAY,SAAS,mBAAmB,GAC7D,OAAO;CAET,IAAI,SAAS;CACb,MAAM,WAAW,SAAyB;EACxC,IAAI,CAAC,KAAK,WAAW,QAAQ,KAAK,SAAS,gBAAgB,OAAO;EAClE,IAAI;GACF,MAAM,QAAQ,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC;GACtC,IAAI,SAAS,OAAO,UAAU,YAAY,EAAE,aAAa,QACvD,OAAO,SAAS,KAAK,UAAU;IAAE,GAAG;IAAO,SAAS,CAAC;GAAE,CAAC;EAE5D,QAAQ,CAER;EACA,OAAO;CACT;CACA,MAAM,OAAO,SAAS,KACnB,YAAY,IAAI,kBAAkB,CAAC,CAAC,CACpC,YACC,IAAI,gBAAgC;EAClC,UAAU,OAAO,YAAY;GAC3B,UAAU;GACV,MAAM,QAAQ,OAAO,MAAM,IAAI;GAC/B,SAAS,MAAM,IAAI,KAAK;GACxB,KAAK,MAAM,QAAQ,OAAO,WAAW,QAAQ,GAAG,QAAQ,IAAI,EAAE,GAAG;EACnE;EACA,MAAM,YAAY;GAChB,IAAI,QAAQ,WAAW,QAAQ,QAAQ,MAAM,CAAC;EAChD;CACF,CAAC,CACH,CAAC,CACA,YAAY,IAAI,kBAAkB,CAAC;CACtC,OAAO,IAAI,SAAS,MAAM;EACxB,QAAQ,SAAS;EACjB,YAAY,SAAS;EACrB,SAAS,SAAS;CACpB,CAAC;AACH;;;;;;;AAQA,eAAsB,uBACpB,UACmB;CACnB,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,IAAI,OAAO;CACX,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,IAAI;EAO5B,IAAI,QAAQ,OAAO,SAAS,YAAY,EAAE,WAAW,OAAO;GAC1D,MAAM,QAAQ,KAAK,SAAS;GAC5B,OAAO,KAAK,UAAU,EACpB,OAAO;IACL,SAAS,KAAK,WAAW,OAAO,WAAW;IAC3C,MAAM,KAAK,QAAQ;IACnB,MAAM,KAAK,gBAAgB,OAAO,QAAQ;GAC5C,EACF,CAAC;EACH;CACF,QAAQ,CAER;CACA,OAAO,IAAI,SAAS,MAAM;EACxB,QAAQ,SAAS;EACjB,YAAY,SAAS;EACrB,SAAS,SAAS;CACpB,CAAC;AACH;;AAGA,eAAsB,kBAAkB,UAAuC;CAC7E,OAAO,SAAS,KACZ,qBAAqB,QAAQ,IAC7B,MAAM,uBAAuB,QAAQ;AAC3C;;;;;;AAOA,SAAgB,mBACd,SACA,SACW;CAGX,MAAM,MAAM,QAAQ,IAAI,KAAK,OAAO;CAKpC,OAAO,OAAO,QAAQ,SAAS;EAC7B,MAAM,EAAE,OAAO,GAAG,WAAW,KAAK,MAChC,OAAO,MAAM,SAAS,WAAW,KAAK,OAAO,IAC/C;EAKA,OAAO,MAAM,kBAAkB,MAJP,IAAI,OAAO,QAAQ;GACzC,mBAAmB;GACnB,GAAI,WAAW,EAAE,QAAQ;EAC3B,CAAC,CACsC;CACzC;AACF;;AAGA,SAAgB,gBAAgB,WAA6C;CAC3E,MAAM,YAAY,aAAa;CAC/B,OAAO,OAAO,OAAO,SACnB,MAAM,kBAAkB,MAAM,UAAU,OAAO,IAAI,CAAC;AACxD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AiModels, BaseAiAutomaticSpeechRecognition, BaseAiTextEmbeddings, BaseAiTextGeneration, BaseAiTextToImage, BaseAiTextToSpeech } from '@cloudflare/workers-types';
|
|
2
|
+
/** Model ids from the Workers AI catalog whose task shape matches `TTask`. */
|
|
3
|
+
type ModelsFor<TTask> = {
|
|
4
|
+
[K in keyof AiModels]: AiModels[K] extends TTask ? K : never;
|
|
5
|
+
}[keyof AiModels];
|
|
6
|
+
/**
|
|
7
|
+
* Chat model id. Catalog ids get autocomplete; any other id works too,
|
|
8
|
+
* including third-party `provider/model` ids routed through AI Gateway
|
|
9
|
+
* (for example `openai/gpt-5.5`).
|
|
10
|
+
*/
|
|
11
|
+
export type CloudflareTextModel = ModelsFor<BaseAiTextGeneration> | (string & {});
|
|
12
|
+
export type CloudflareEmbeddingModel = ModelsFor<BaseAiTextEmbeddings> | (string & {});
|
|
13
|
+
export type CloudflareImageModel = ModelsFor<BaseAiTextToImage> | (string & {});
|
|
14
|
+
export type CloudflareTTSModel = ModelsFor<BaseAiTextToSpeech> | '@cf/deepgram/aura-1' | '@cf/deepgram/aura-2-en' | '@cf/deepgram/aura-2-es' | (string & {});
|
|
15
|
+
export type CloudflareTranscriptionModel = ModelsFor<BaseAiAutomaticSpeechRecognition> | '@cf/openai/whisper-large-v3-turbo' | '@cf/deepgram/nova-3' | (string & {});
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CloudflareConfig } from './config.js';
|
|
2
|
+
export type RunInputs = Record<string, unknown>;
|
|
3
|
+
export interface RunBinary {
|
|
4
|
+
/** Input field that carries the bytes on the binding path. */
|
|
5
|
+
field: string;
|
|
6
|
+
body: Uint8Array | ArrayBuffer | Blob;
|
|
7
|
+
contentType: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Runs a Workers AI model with its native task inputs (embeddings, image,
|
|
11
|
+
* speech, transcription) through the binding or the REST `/ai/run` endpoint.
|
|
12
|
+
*
|
|
13
|
+
* Returns the model's decoded output: an object for JSON tasks, or bytes
|
|
14
|
+
* (`Uint8Array` / `ReadableStream`) for binary media outputs.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runModel(config: CloudflareConfig, model: string, inputs: RunInputs, options?: {
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
binary?: RunBinary;
|
|
19
|
+
}): Promise<unknown>;
|
|
20
|
+
/** Base64-encodes a binary model output, whatever shape it arrived in. */
|
|
21
|
+
export declare function outputToBase64(output: unknown): Promise<string>;
|