UNPKG

workers-ai-provider

Version:

Workers AI Provider for the vercel AI SDK

1,000 lines (985 loc) 30.7 kB
var __defProp = Object.defineProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); // src/autorag-chat-language-model.ts import { UnsupportedFunctionalityError } from "@ai-sdk/provider"; // src/convert-to-workersai-chat-messages.ts function convertToWorkersAIChatMessages(prompt) { const messages = []; const images = []; for (const { role, content } of prompt) { switch (role) { case "system": { messages.push({ content, role: "system" }); break; } case "user": { messages.push({ content: content.map((part) => { switch (part.type) { case "text": { return part.text; } case "image": { if (part.image instanceof Uint8Array) { images.push({ image: part.image, mimeType: part.mimeType, providerMetadata: part.providerMetadata }); } return ""; } } }).join("\n"), role: "user" }); break; } case "assistant": { let text = ""; const toolCalls = []; for (const part of content) { switch (part.type) { case "text": { text += part.text; break; } case "reasoning": { text += part.text; break; } case "tool-call": { text = JSON.stringify({ name: part.toolName, parameters: part.args }); toolCalls.push({ function: { arguments: JSON.stringify(part.args), name: part.toolName }, id: part.toolCallId, type: "function" }); break; } default: { const exhaustiveCheck = part; throw new Error(`Unsupported part type: ${exhaustiveCheck.type}`); } } } messages.push({ content: text, role: "assistant", tool_calls: toolCalls.length > 0 ? toolCalls.map(({ function: { name, arguments: args } }) => ({ function: { arguments: args, name }, id: "null", type: "function" })) : void 0 }); break; } case "tool": { for (const toolResponse of content) { messages.push({ content: JSON.stringify(toolResponse.result), name: toolResponse.toolName, role: "tool" }); } break; } default: { const exhaustiveCheck = role; throw new Error(`Unsupported role: ${exhaustiveCheck}`); } } } return { images, messages }; } // src/map-workersai-usage.ts function mapWorkersAIUsage(output) { const usage = output.usage ?? { completion_tokens: 0, prompt_tokens: 0 }; return { completionTokens: usage.completion_tokens, promptTokens: usage.prompt_tokens }; } // ../../node_modules/.pnpm/fetch-event-stream@0.1.5/node_modules/fetch-event-stream/esm/deps/jsr.io/@std/streams/0.221.0/text_line_stream.js var _currentLine; var TextLineStream = class extends TransformStream { /** Constructs a new instance. */ constructor(options = { allowCR: false }) { super({ transform: (chars, controller) => { chars = __privateGet(this, _currentLine) + chars; while (true) { const lfIndex = chars.indexOf("\n"); const crIndex = options.allowCR ? chars.indexOf("\r") : -1; if (crIndex !== -1 && crIndex !== chars.length - 1 && (lfIndex === -1 || lfIndex - 1 > crIndex)) { controller.enqueue(chars.slice(0, crIndex)); chars = chars.slice(crIndex + 1); continue; } if (lfIndex === -1) break; const endIndex = chars[lfIndex - 1] === "\r" ? lfIndex - 1 : lfIndex; controller.enqueue(chars.slice(0, endIndex)); chars = chars.slice(lfIndex + 1); } __privateSet(this, _currentLine, chars); }, flush: (controller) => { if (__privateGet(this, _currentLine) === "") return; const currentLine = options.allowCR && __privateGet(this, _currentLine).endsWith("\r") ? __privateGet(this, _currentLine).slice(0, -1) : __privateGet(this, _currentLine); controller.enqueue(currentLine); } }); __privateAdd(this, _currentLine, ""); } }; _currentLine = new WeakMap(); // ../../node_modules/.pnpm/fetch-event-stream@0.1.5/node_modules/fetch-event-stream/esm/utils.js function stream(input) { let decoder = new TextDecoderStream(); let split2 = new TextLineStream({ allowCR: true }); return input.pipeThrough(decoder).pipeThrough(split2); } function split(input) { let rgx = /[:]\s*/; let match = rgx.exec(input); let idx = match && match.index; if (idx) { return [ input.substring(0, idx), input.substring(idx + match[0].length) ]; } } // ../../node_modules/.pnpm/fetch-event-stream@0.1.5/node_modules/fetch-event-stream/esm/mod.js async function* events(res, signal) { if (!res.body) return; let iter = stream(res.body); let line, reader = iter.getReader(); let event; for (; ; ) { if (signal && signal.aborted) { return reader.cancel(); } line = await reader.read(); if (line.done) return; if (!line.value) { if (event) yield event; event = void 0; continue; } let [field, value] = split(line.value) || []; if (!field) continue; if (field === "data") { event || (event = {}); event[field] = event[field] ? event[field] + "\n" + value : value; } else if (field === "event") { event || (event = {}); event[field] = value; } else if (field === "id") { event || (event = {}); event[field] = +value || value; } else if (field === "retry") { event || (event = {}); event[field] = +value || void 0; } } } // src/utils.ts function createRun(config) { const { accountId, apiKey } = config; return async function run(model, inputs, options) { const { gateway, prefix, extraHeaders, returnRawResponse, ...passthroughOptions } = options || {}; const urlParams = new URLSearchParams(); for (const [key, value] of Object.entries(passthroughOptions)) { try { const valueStr = value.toString(); if (!valueStr) { continue; } urlParams.append(key, valueStr); } catch (_error) { throw new Error( `Value for option '${key}' is not able to be coerced into a string.` ); } } const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/${model}${urlParams ? `?${urlParams}` : ""}`; const headers = { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }; const body = JSON.stringify(inputs); const response = await fetch(url, { body, headers, method: "POST" }); if (returnRawResponse) { return response; } if (inputs.stream === true) { if (response.body) { return response.body; } throw new Error("No readable body available for streaming."); } const data = await response.json(); return data.result; }; } function prepareToolsAndToolChoice(mode) { const tools = mode.tools?.length ? mode.tools : void 0; if (tools == null) { return { tool_choice: void 0, tools: void 0 }; } const mappedTools = tools.map((tool) => ({ function: { // @ts-expect-error - description is not a property of tool description: tool.description, name: tool.name, // @ts-expect-error - parameters is not a property of tool parameters: tool.parameters }, type: "function" })); const toolChoice = mode.toolChoice; if (toolChoice == null) { return { tool_choice: void 0, tools: mappedTools }; } const type = toolChoice.type; switch (type) { case "auto": return { tool_choice: type, tools: mappedTools }; case "none": return { tool_choice: type, tools: mappedTools }; case "required": return { tool_choice: "any", tools: mappedTools }; // workersAI does not support tool mode directly, // so we filter the tools and force the tool choice through 'any' case "tool": return { tool_choice: "any", tools: mappedTools.filter((tool) => tool.function.name === toolChoice.toolName) }; default: { const exhaustiveCheck = type; throw new Error(`Unsupported tool choice type: ${exhaustiveCheck}`); } } } function lastMessageWasUser(messages) { return messages.length > 0 && messages[messages.length - 1].role === "user"; } function mergePartialToolCalls(partialCalls) { const mergedCallsByIndex = {}; for (const partialCall of partialCalls) { const index = partialCall.index; if (!mergedCallsByIndex[index]) { mergedCallsByIndex[index] = { function: { arguments: "", name: partialCall.function?.name || "" }, id: partialCall.id || "", type: partialCall.type || "" }; } else { if (partialCall.id) { mergedCallsByIndex[index].id = partialCall.id; } if (partialCall.type) { mergedCallsByIndex[index].type = partialCall.type; } if (partialCall.function?.name) { mergedCallsByIndex[index].function.name = partialCall.function.name; } } if (partialCall.function?.arguments) { mergedCallsByIndex[index].function.arguments += partialCall.function.arguments; } } return Object.values(mergedCallsByIndex); } function processToolCall(toolCall) { if (toolCall.function && toolCall.id) { return { args: typeof toolCall.function.arguments === "string" ? toolCall.function.arguments : JSON.stringify(toolCall.function.arguments || {}), toolCallId: toolCall.id, toolCallType: "function", toolName: toolCall.function.name }; } return { args: typeof toolCall.arguments === "string" ? toolCall.arguments : JSON.stringify(toolCall.arguments || {}), toolCallId: toolCall.name, toolCallType: "function", toolName: toolCall.name }; } function processToolCalls(output) { if (output.tool_calls && Array.isArray(output.tool_calls)) { return output.tool_calls.map((toolCall) => { const processedToolCall = processToolCall(toolCall); return processedToolCall; }); } if (output?.choices?.[0]?.message?.tool_calls && Array.isArray(output.choices[0].message.tool_calls)) { return output.choices[0].message.tool_calls.map((toolCall) => { const processedToolCall = processToolCall(toolCall); return processedToolCall; }); } return []; } function processPartialToolCalls(partialToolCalls) { const mergedToolCalls = mergePartialToolCalls(partialToolCalls); return processToolCalls({ tool_calls: mergedToolCalls }); } // src/streaming.ts function getMappedStream(response) { const chunkEvent = events(response); let usage = { completionTokens: 0, promptTokens: 0 }; const partialToolCalls = []; return new ReadableStream({ async start(controller) { for await (const event of chunkEvent) { if (!event.data) { continue; } if (event.data === "[DONE]") { break; } const chunk = JSON.parse(event.data); if (chunk.usage) { usage = mapWorkersAIUsage(chunk); } if (chunk.tool_calls) { partialToolCalls.push(...chunk.tool_calls); } chunk.response?.length && controller.enqueue({ textDelta: chunk.response, type: "text-delta" }); chunk?.choices?.[0]?.delta?.reasoning_content?.length && controller.enqueue({ type: "reasoning", textDelta: chunk.choices[0].delta.reasoning_content }); } if (partialToolCalls.length > 0) { const toolCalls = processPartialToolCalls(partialToolCalls); toolCalls.map((toolCall) => { controller.enqueue({ type: "tool-call", ...toolCall }); }); } controller.enqueue({ finishReason: "stop", type: "finish", usage }); controller.close(); } }); } // src/autorag-chat-language-model.ts var AutoRAGChatLanguageModel = class { constructor(modelId, settings, config) { __publicField(this, "specificationVersion", "v1"); __publicField(this, "defaultObjectGenerationMode", "json"); __publicField(this, "modelId"); __publicField(this, "settings"); __publicField(this, "config"); this.modelId = modelId; this.settings = settings; this.config = config; } get provider() { return this.config.provider; } getArgs({ mode, prompt, frequencyPenalty, presencePenalty }) { const type = mode.type; const warnings = []; if (frequencyPenalty != null) { warnings.push({ setting: "frequencyPenalty", type: "unsupported-setting" }); } if (presencePenalty != null) { warnings.push({ setting: "presencePenalty", type: "unsupported-setting" }); } const baseArgs = { // messages: messages: convertToWorkersAIChatMessages(prompt), // model id: model: this.modelId }; switch (type) { case "regular": { return { args: { ...baseArgs, ...prepareToolsAndToolChoice(mode) }, warnings }; } case "object-json": { return { args: { ...baseArgs, response_format: { json_schema: mode.schema, type: "json_schema" }, tools: void 0 }, warnings }; } case "object-tool": { return { args: { ...baseArgs, tool_choice: "any", tools: [{ function: mode.tool, type: "function" }] }, warnings }; } // @ts-expect-error - this is unreachable code // TODO: fixme case "object-grammar": { throw new UnsupportedFunctionalityError({ functionality: "object-grammar mode" }); } default: { const exhaustiveCheck = type; throw new Error(`Unsupported type: ${exhaustiveCheck}`); } } } async doGenerate(options) { const { args, warnings } = this.getArgs(options); const { messages } = convertToWorkersAIChatMessages(options.prompt); const output = await this.config.binding.aiSearch({ query: messages.map(({ content, role }) => `${role}: ${content}`).join("\n\n") }); return { finishReason: "stop", rawCall: { rawPrompt: args.messages, rawSettings: args }, sources: output.data.map(({ file_id, filename, score }) => ({ id: file_id, providerMetadata: { attributes: { score } }, sourceType: "url", url: filename })), // TODO: mapWorkersAIFinishReason(response.finish_reason), text: output.response, toolCalls: processToolCalls(output), usage: mapWorkersAIUsage(output), warnings }; } async doStream(options) { const { args, warnings } = this.getArgs(options); const { messages } = convertToWorkersAIChatMessages(options.prompt); const query = messages.map(({ content, role }) => `${role}: ${content}`).join("\n\n"); const response = await this.config.binding.aiSearch({ query, stream: true }); return { rawCall: { rawPrompt: args.messages, rawSettings: args }, stream: getMappedStream(response), warnings }; } }; // src/workers-ai-embedding-model.ts import { TooManyEmbeddingValuesForCallError } from "@ai-sdk/provider"; var WorkersAIEmbeddingModel = class { constructor(modelId, settings, config) { /** * Semantic version of the {@link EmbeddingModelV1} specification implemented * by this class. It never changes. */ __publicField(this, "specificationVersion", "v1"); __publicField(this, "modelId"); __publicField(this, "config"); __publicField(this, "settings"); this.modelId = modelId; this.settings = settings; this.config = config; } /** * Provider name exposed for diagnostics and error reporting. */ get provider() { return this.config.provider; } get maxEmbeddingsPerCall() { const maxEmbeddingsPerCall = this.modelId === "@cf/baai/bge-large-en-v1.5" ? 1500 : 3e3; return this.settings.maxEmbeddingsPerCall ?? maxEmbeddingsPerCall; } get supportsParallelCalls() { return this.settings.supportsParallelCalls ?? true; } async doEmbed({ values }) { if (values.length > this.maxEmbeddingsPerCall) { throw new TooManyEmbeddingValuesForCallError({ maxEmbeddingsPerCall: this.maxEmbeddingsPerCall, modelId: this.modelId, provider: this.provider, values }); } const { gateway, ...passthroughOptions } = this.settings; const response = await this.config.binding.run( this.modelId, // @ts-ignore: Error introduced with "@cloudflare/workers-types": "^4.20250617.0" { text: values }, { gateway: this.config.gateway ?? gateway, ...passthroughOptions } ); return { // @ts-ignore: Error introduced with "@cloudflare/workers-types": "^4.20250617.0" embeddings: response.data }; } }; // src/workersai-chat-language-model.ts import { UnsupportedFunctionalityError as UnsupportedFunctionalityError2 } from "@ai-sdk/provider"; // src/map-workersai-finish-reason.ts function mapWorkersAIFinishReason(finishReasonOrResponse) { let finishReason; if (typeof finishReasonOrResponse === "string" || finishReasonOrResponse === null || finishReasonOrResponse === void 0) { finishReason = finishReasonOrResponse; } else if (typeof finishReasonOrResponse === "object" && finishReasonOrResponse !== null) { const response = finishReasonOrResponse; if ("choices" in response && Array.isArray(response.choices) && response.choices.length > 0) { finishReason = response.choices[0].finish_reason; } else if ("finish_reason" in response) { finishReason = response.finish_reason; } else { finishReason = void 0; } } switch (finishReason) { case "stop": return "stop"; case "length": case "model_length": return "length"; case "tool_calls": return "tool-calls"; case "error": return "error"; case "other": return "other"; case "unknown": return "unknown"; default: return "stop"; } } // src/workersai-chat-language-model.ts var WorkersAIChatLanguageModel = class { constructor(modelId, settings, config) { __publicField(this, "specificationVersion", "v1"); __publicField(this, "defaultObjectGenerationMode", "json"); __publicField(this, "modelId"); __publicField(this, "settings"); __publicField(this, "config"); this.modelId = modelId; this.settings = settings; this.config = config; } get provider() { return this.config.provider; } getArgs({ mode, maxTokens, temperature, topP, frequencyPenalty, presencePenalty, seed }) { const type = mode.type; const warnings = []; if (frequencyPenalty != null) { warnings.push({ setting: "frequencyPenalty", type: "unsupported-setting" }); } if (presencePenalty != null) { warnings.push({ setting: "presencePenalty", type: "unsupported-setting" }); } const baseArgs = { // standardized settings: max_tokens: maxTokens, // model id: model: this.modelId, random_seed: seed, // model specific settings: safe_prompt: this.settings.safePrompt, temperature, top_p: topP }; switch (type) { case "regular": { return { args: { ...baseArgs, ...prepareToolsAndToolChoice(mode) }, warnings }; } case "object-json": { return { args: { ...baseArgs, response_format: { json_schema: mode.schema, type: "json_schema" }, tools: void 0 }, warnings }; } case "object-tool": { return { args: { ...baseArgs, tool_choice: "any", tools: [{ function: mode.tool, type: "function" }] }, warnings }; } // @ts-expect-error - this is unreachable code // TODO: fixme case "object-grammar": { throw new UnsupportedFunctionalityError2({ functionality: "object-grammar mode" }); } default: { const exhaustiveCheck = type; throw new Error(`Unsupported type: ${exhaustiveCheck}`); } } } async doGenerate(options) { const { args, warnings } = this.getArgs(options); const { gateway, safePrompt, ...passthroughOptions } = this.settings; const { messages, images } = convertToWorkersAIChatMessages(options.prompt); if (images.length !== 0 && images.length !== 1) { throw new Error("Multiple images are not yet supported as input"); } const imagePart = images[0]; const output = await this.config.binding.run( args.model, { max_tokens: args.max_tokens, messages, temperature: args.temperature, tools: args.tools, top_p: args.top_p, // Convert Uint8Array to Array of integers for Llama 3.2 Vision model // TODO: maybe use the base64 string version? ...imagePart ? { image: Array.from(imagePart.image) } : {}, // @ts-expect-error response_format not yet added to types response_format: args.response_format }, { gateway: this.config.gateway ?? gateway, ...passthroughOptions } ); if (output instanceof ReadableStream) { throw new Error("This shouldn't happen"); } return { finishReason: mapWorkersAIFinishReason(output), rawCall: { rawPrompt: messages, rawSettings: args }, rawResponse: { body: output }, text: typeof output.response === "object" && output.response !== null ? JSON.stringify(output.response) : output.response, toolCalls: processToolCalls(output), // @ts-ignore: Missing types reasoning: output?.choices?.[0]?.message?.reasoning_content, usage: mapWorkersAIUsage(output), warnings }; } async doStream(options) { const { args, warnings } = this.getArgs(options); const { messages, images } = convertToWorkersAIChatMessages(options.prompt); if (args.tools?.length && lastMessageWasUser(messages)) { const response2 = await this.doGenerate(options); if (response2 instanceof ReadableStream) { throw new Error("This shouldn't happen"); } return { rawCall: { rawPrompt: messages, rawSettings: args }, stream: new ReadableStream({ async start(controller) { if (response2.text) { controller.enqueue({ textDelta: response2.text, type: "text-delta" }); } if (response2.toolCalls) { for (const toolCall of response2.toolCalls) { controller.enqueue({ type: "tool-call", ...toolCall }); } } if (response2.reasoning && typeof response2.reasoning === "string") { controller.enqueue({ type: "reasoning", textDelta: response2.reasoning }); } controller.enqueue({ finishReason: mapWorkersAIFinishReason(response2), type: "finish", usage: response2.usage }); controller.close(); } }), warnings }; } const { gateway, ...passthroughOptions } = this.settings; if (images.length !== 0 && images.length !== 1) { throw new Error("Multiple images are not yet supported as input"); } const imagePart = images[0]; const response = await this.config.binding.run( args.model, { max_tokens: args.max_tokens, messages, stream: true, temperature: args.temperature, tools: args.tools, top_p: args.top_p, // Convert Uint8Array to Array of integers for Llama 3.2 Vision model // TODO: maybe use the base64 string version? ...imagePart ? { image: Array.from(imagePart.image) } : {}, // @ts-expect-error response_format not yet added to types response_format: args.response_format }, { gateway: this.config.gateway ?? gateway, ...passthroughOptions } ); if (!(response instanceof ReadableStream)) { throw new Error("This shouldn't happen"); } return { rawCall: { rawPrompt: messages, rawSettings: args }, stream: getMappedStream(new Response(response)), warnings }; } }; // src/workersai-image-model.ts var WorkersAIImageModel = class { constructor(modelId, settings, config) { this.modelId = modelId; this.settings = settings; this.config = config; __publicField(this, "specificationVersion", "v1"); } get maxImagesPerCall() { return this.settings.maxImagesPerCall ?? 1; } get provider() { return this.config.provider; } async doGenerate({ prompt, n, size, aspectRatio, seed // headers, // abortSignal, }) { const { width, height } = getDimensionsFromSizeString(size); const warnings = []; if (aspectRatio != null) { warnings.push({ details: "This model does not support aspect ratio. Use `size` instead.", setting: "aspectRatio", type: "unsupported-setting" }); } const generateImage = async () => { const outputStream = await this.config.binding.run( this.modelId, { height, prompt, seed, width } ); return streamToUint8Array(outputStream); }; const images = await Promise.all( Array.from({ length: n }, () => generateImage()) ); return { images, response: { headers: {}, modelId: this.modelId, timestamp: /* @__PURE__ */ new Date() }, warnings }; } }; function getDimensionsFromSizeString(size) { const [width, height] = size?.split("x") ?? [void 0, void 0]; return { height: parseInteger(height), width: parseInteger(width) }; } function parseInteger(value) { if (value === "" || !value) return void 0; const number = Number(value); return Number.isInteger(number) ? number : void 0; } async function streamToUint8Array(stream2) { const reader = stream2.getReader(); const chunks = []; let totalLength = 0; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); totalLength += value.length; } const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; } // src/index.ts function createWorkersAI(options) { let binding; if (options.binding) { binding = options.binding; } else { const { accountId, apiKey } = options; binding = { run: createRun({ accountId, apiKey }) }; } if (!binding) { throw new Error("Either a binding or credentials must be provided."); } const createChatModel = (modelId, settings = {}) => new WorkersAIChatLanguageModel(modelId, settings, { binding, gateway: options.gateway, provider: "workersai.chat" }); const createImageModel = (modelId, settings = {}) => new WorkersAIImageModel(modelId, settings, { binding, gateway: options.gateway, provider: "workersai.image" }); const createEmbeddingModel = (modelId, settings = {}) => new WorkersAIEmbeddingModel(modelId, settings, { binding, gateway: options.gateway, provider: "workersai.embedding" }); const provider = (modelId, settings) => { if (new.target) { throw new Error("The WorkersAI model function cannot be called with the new keyword."); } return createChatModel(modelId, settings); }; provider.chat = createChatModel; provider.embedding = createEmbeddingModel; provider.textEmbedding = createEmbeddingModel; provider.textEmbeddingModel = createEmbeddingModel; provider.image = createImageModel; provider.imageModel = createImageModel; return provider; } function createAutoRAG(options) { const binding = options.binding; const createChatModel = (settings = {}) => ( // @ts-ignore Needs fix from @cloudflare/workers-types for custom types new AutoRAGChatLanguageModel("@cf/meta/llama-3.3-70b-instruct-fp8-fast", settings, { binding, provider: "autorag.chat" }) ); const provider = (settings) => { if (new.target) { throw new Error("The WorkersAI model function cannot be called with the new keyword."); } return createChatModel(settings); }; provider.chat = createChatModel; return provider; } export { createAutoRAG, createWorkersAI }; //# sourceMappingURL=index.js.map