@tanstack/ai-cloudflare 0.1.8 → 0.2.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.
|
@@ -32,7 +32,7 @@ var CloudflareTextAdapter = class extends OpenAIBaseChatCompletionsTextAdapter {
|
|
|
32
32
|
kind = "text";
|
|
33
33
|
name = "cloudflare";
|
|
34
34
|
constructor(config, model) {
|
|
35
|
-
super(model, "cloudflare", createClient(config));
|
|
35
|
+
super(model, "cloudflare", createClient(config), config);
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Workers AI validates `messages[].content` as a string, so a tool-call-only
|
|
@@ -1 +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,
|
|
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), 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,GAAG,MAAM;CACzD;;;;;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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Ai, GatewayOptions } from '@cloudflare/workers-types';
|
|
2
2
|
import { ClientOptions } from 'openai';
|
|
3
|
+
import { OpenAIBaseTextAdapterOptions } from '@tanstack/openai-base';
|
|
3
4
|
/**
|
|
4
5
|
* AI Gateway routing options. `id` is the gateway id (use `"default"` for the
|
|
5
6
|
* account's auto-created gateway). The remaining fields are per-request
|
|
@@ -35,7 +36,7 @@ export interface CloudflareRestConfig {
|
|
|
35
36
|
export interface CloudflareTextRestConfig extends CloudflareRestConfig, Omit<ClientOptions, 'apiKey' | 'fetch'> {
|
|
36
37
|
}
|
|
37
38
|
export type CloudflareConfig = CloudflareBindingConfig | CloudflareRestConfig;
|
|
38
|
-
export type CloudflareTextConfig = CloudflareBindingConfig | CloudflareTextRestConfig;
|
|
39
|
+
export type CloudflareTextConfig = (CloudflareBindingConfig | CloudflareTextRestConfig) & OpenAIBaseTextAdapterOptions;
|
|
39
40
|
/**
|
|
40
41
|
* What the env-reading factories accept: a binding, or REST fields with any
|
|
41
42
|
* missing ones read from `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`.
|
|
@@ -1 +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
|
|
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'\nimport type { OpenAIBaseTextAdapterOptions } from '@tanstack/openai-base'\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 OpenAIBaseTextAdapterOptions\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":";;AA4DA,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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-cloudflare",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Cloudflare adapter for TanStack AI — Workers AI chat, embeddings, images, speech, transcription, and summarization over the AI binding or REST, with AI Gateway routing for any provider.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
"@cloudflare/workers-types": "^4.20260317.1",
|
|
58
58
|
"openai": "^6.41.0",
|
|
59
59
|
"@tanstack/ai-utils": "^0.4.1",
|
|
60
|
-
"@tanstack/openai-base": "^0.
|
|
60
|
+
"@tanstack/openai-base": "^0.12.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@vitest/coverage-v8": "4.1.10",
|
|
64
64
|
"vite": "^8.2.1",
|
|
65
|
-
"@tanstack/ai": "0.
|
|
65
|
+
"@tanstack/ai": "0.63.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@tanstack/ai": "^0.
|
|
68
|
+
"@tanstack/ai": "^0.63.0"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "vite build",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"lint:fix": "oxlint src --type-aware --fix",
|
|
74
74
|
"test:build": "publint --strict",
|
|
75
75
|
"test:oxlint": "oxlint src --type-aware",
|
|
76
|
+
"test:coverage": "vitest run --coverage --coverage.include='src/**' --coverage.reporter=text-summary --coverage.reporter=json-summary",
|
|
76
77
|
"test:lib": "vitest run",
|
|
77
78
|
"test:lib:dev": "pnpm test:lib --watch",
|
|
78
79
|
"test:types": "tsc"
|
package/src/adapters/text.ts
CHANGED
|
@@ -82,7 +82,7 @@ export class CloudflareTextAdapter<
|
|
|
82
82
|
override readonly name = 'cloudflare' as const
|
|
83
83
|
|
|
84
84
|
constructor(config: CloudflareTextConfig, model: TModel) {
|
|
85
|
-
super(model, 'cloudflare', createClient(config))
|
|
85
|
+
super(model, 'cloudflare', createClient(config), config)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
package/src/utils/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getApiKeyFromEnv } from '@tanstack/ai-utils'
|
|
2
2
|
import type { Ai, GatewayOptions } from '@cloudflare/workers-types'
|
|
3
3
|
import type { ClientOptions } from 'openai'
|
|
4
|
+
import type { OpenAIBaseTextAdapterOptions } from '@tanstack/openai-base'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* AI Gateway routing options. `id` is the gateway id (use `"default"` for the
|
|
@@ -43,9 +44,11 @@ export interface CloudflareTextRestConfig
|
|
|
43
44
|
|
|
44
45
|
export type CloudflareConfig = CloudflareBindingConfig | CloudflareRestConfig
|
|
45
46
|
|
|
46
|
-
export type CloudflareTextConfig =
|
|
47
|
+
export type CloudflareTextConfig = (
|
|
47
48
|
| CloudflareBindingConfig
|
|
48
49
|
| CloudflareTextRestConfig
|
|
50
|
+
) &
|
|
51
|
+
OpenAIBaseTextAdapterOptions
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
54
|
* What the env-reading factories accept: a binding, or REST fields with any
|