Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369import { createAnthropic } from "@ai-sdk/anthropic";import { wrapLanguageModel, type LanguageModel } from "ai";import { createWorkersAI } from "workers-ai-provider";import type { Secret } from "../configuration/secrets.ts";import type { ModelConfiguration } from "./model-settings.ts";import type { DiagnosticEvent } from "../shared/diagnostics.ts";import { MODEL_PROVIDERS, missingProviderKey,} from "../shared/model-providers.ts";import { createGatewayModel } from "./gateway-model.ts";
const INKLING_MODEL = "thinkingmachines/inkling-256k";
// Keep wire-specific options at the provider boundary. Think passes these to// every step; omission preserves the provider's behavior for existing settings.export function modelProviderOptions(configuration: ModelConfiguration) { if (!configuration.effort) return undefined; if (configuration.provider === "openrouter") return { openrouter: { reasoning: { effort: configuration.effort } } }; return { anthropic: { effort: configuration.effort, ...(configuration.provider === "anthropic" ? { thinking: { type: "adaptive" as const } } : {}), }, };}
function reasoningMetadata(metadata: unknown) { if (!metadata || typeof metadata !== "object" || !("anthropic" in metadata)) return undefined; const value = metadata.anthropic; if (!value || typeof value !== "object") return undefined; const fields = value as Record<string, unknown>; const anthropic: Record<string, string> = {}; for (const key of ["signature", "redactedData"] as const) if (typeof fields[key] === "string") anthropic[key] = fields[key]; return Object.keys(anthropic).length ? { anthropic } : undefined;}
function createCloudflareAnthropicModel( binding: Ai, model: typeof INKLING_MODEL, sessionAffinity?: string,) { const fetchThroughBinding: typeof fetch = async (_input, init) => { const body = JSON.parse(String(init?.body ?? "{}")) as Record< string, unknown >; delete body.model; const extraHeaders = sessionAffinity ? { "x-session-affinity": sessionAffinity } : undefined; return ( binding as unknown as { run( model: string, input: Record<string, unknown>, options: Record<string, unknown>, ): Promise<Response>; } ).run(model, body, { gateway: { id: "default" }, returnRawResponse: true, ...(extraHeaders ? { extraHeaders } : {}), ...(init?.signal ? { signal: init.signal } : {}), }); }; return createAnthropic({ apiKey: "unused", fetch: fetchThroughBinding })( model, );}
export function createConfiguredModel( binding: Ai, configuration: ModelConfiguration, key?: Secret, sessionAffinity?: string,): Exclude<LanguageModel, string> { if ( configuration.provider === "workers-ai" && configuration.model === INKLING_MODEL ) return createCloudflareAnthropicModel( binding, configuration.model, sessionAffinity, ); if (configuration.provider === "workers-ai") return createWorkersAI({ binding })(configuration.model, { gateway: { id: "default" }, sessionAffinity, }); const provider = MODEL_PROVIDERS[configuration.provider]; if (!key) throw new Error(missingProviderKey(configuration.provider)); if (provider.transport.kind === "gateway") return createGatewayModel(binding, configuration, key); // Fixed official endpoint: settings cannot redirect a credential to another host. return createAnthropic({ apiKey: key.reveal(), baseURL: provider.transport.baseURL, })(configuration.model);}
function providerError( error: unknown, provider: ModelConfiguration["provider"], aborted = false,): Error { if (aborted || (error instanceof Error && error.name === "AbortError")) return new DOMException("Model request cancelled", "AbortError"); const status = typeof error === "object" && error !== null && "statusCode" in error ? error.statusCode : undefined; const name = MODEL_PROVIDERS[provider].name; if (status === 402 && provider === "workers-ai") return new Error( "Cloudflare AI Gateway balance is insufficient; add credits and try again", ); if (status === 402) return new Error( `${name} allowance or balance is insufficient; check your plan and usage`, ); if (status === 404 && MODEL_PROVIDERS[provider].transport.kind === "gateway") return new Error( `${name} gateway route or model is unavailable; check the AI Gateway custom provider setup`, ); if (status === 401 || status === 403) return new Error( `${name} rejected authentication; check provider credentials and access`, ); if (status === 429) return new Error(`${name} rate limit reached; try again later`); return new Error(`${name} request failed; check model access and try again`);}
// Provider errors can carry request headers, response bodies and arbitrary text.// Discard them before AI SDK/Think telemetry, persistence and stream serialization.// Keep the original abort signal and stream backpressure/cancellation semantics.export interface ModelObservation { phase: "start" | "finish"; attemptId: string; status: "started" | "completed" | "error" | "aborted"; durationMs: number | null; httpStatus?: number | null; failureStage?: "request" | "stream-read" | "stream-event"; errorCategory?: DiagnosticEvent["details"]["errorCategory"]; gatewayErrorCode?: number | null; cfRay?: string | null;}
export function protectModel( model: Exclude<LanguageModel, string>, provider: ModelConfiguration["provider"], observe?: (event: ModelObservation) => void,) { const attempt = () => { const attemptId = crypto.randomUUID(); const startedAt = performance.now(); let finished = false; const emit = (event: ModelObservation) => { try { observe?.(event); } catch { /* Diagnostics cannot affect model execution. */ } }; emit({ phase: "start", attemptId, status: "started", durationMs: null }); return ( status: "completed" | "error" | "aborted", error?: unknown, failureStage?: ModelObservation["failureStage"], ) => { if (finished) return; finished = true; const rawStatus = typeof error === "object" && error !== null && "statusCode" in error ? error.statusCode : undefined; const httpStatus = typeof rawStatus === "number" && Number.isInteger(rawStatus) && rawStatus >= 400 && rawStatus <= 599 ? rawStatus : null; let gatewayErrorCode: number | null = null; let cfRay: string | null = null; if (typeof error === "object" && error !== null) { // Inspect a bounded envelope, never retain its message/body or arbitrary headers. if ( "responseBody" in error && typeof error.responseBody === "string" && error.responseBody.length <= 8192 ) { try { const body = JSON.parse(error.responseBody); if ( body?.name === "AiGatewayError" && Number.isInteger(body.internalCode) && body.internalCode >= 1000 && body.internalCode <= 9999 ) gatewayErrorCode = body.internalCode; } catch { /* Non-JSON provider errors remain unclassified. */ } } if ( "responseHeaders" in error && typeof error.responseHeaders === "object" && error.responseHeaders !== null && "cf-ray" in error.responseHeaders ) { const value = error.responseHeaders["cf-ray"]; if ( typeof value === "string" && /^[a-f0-9]{16}-[A-Z]{3}$/.test(value) ) cfRay = value; } } const errorCategory: ModelObservation["errorCategory"] = httpStatus === 401 || httpStatus === 403 ? "authentication" : httpStatus === 402 ? "billing" : httpStatus === 429 ? "rate-limit" : httpStatus === 404 ? "not-found" : httpStatus !== null && httpStatus >= 500 ? "upstream-unavailable" : httpStatus !== null ? "invalid-request" : "unknown"; emit({ phase: "finish", attemptId, status, durationMs: Math.max(0, performance.now() - startedAt), ...(status === "error" ? { httpStatus, failureStage, errorCategory, gatewayErrorCode, cfRay } : {}), }); }; }; return wrapLanguageModel({ model, middleware: { wrapGenerate: async ({ doGenerate, params }) => { const finish = attempt(); try { const result = await doGenerate(); finish("completed"); return { ...result, content: result.content.map((part) => ({ ...part, providerMetadata: part.type === "reasoning" ? reasoningMetadata(part.providerMetadata) : undefined, })), request: undefined, response: undefined, warnings: [], providerMetadata: undefined, }; } catch (error) { finish( params.abortSignal?.aborted || (error instanceof Error && error.name === "AbortError") ? "aborted" : "error", error, "request", ); throw providerError(error, provider, params.abortSignal?.aborted); } }, wrapStream: async ({ doStream, params }) => { const finish = attempt(); try { const result = await doStream(); const reader = result.stream.getReader(); const stream = new ReadableStream({ async pull(controller) { try { let next = await reader.read(); while (!next.done && next.value.type === "raw") next = await reader.read(); const { value, done } = next; if (done) { finish(params.abortSignal?.aborted ? "aborted" : "completed"); controller.close(); return; } if (value.type === "error") { finish( params.abortSignal?.aborted ? "aborted" : "error", value.error, "stream-event", ); controller.enqueue({ type: "error", error: providerError( value.error, provider, params.abortSignal?.aborted, ), }); return; } if (value.type === "stream-start") { controller.enqueue({ ...value, warnings: [] }); return; } const chunk = { ...value }; if ("providerMetadata" in chunk) { // Signed/redacted reasoning is protocol state needed by the // next tool step, not telemetry. Keep only those exact fields. const metadata = chunk.type.startsWith("reasoning-") ? reasoningMetadata(chunk.providerMetadata) : undefined; delete chunk.providerMetadata; if (metadata) chunk.providerMetadata = metadata; } controller.enqueue(chunk); } catch (error) { finish( params.abortSignal?.aborted || (error instanceof Error && error.name === "AbortError") ? "aborted" : "error", error, "stream-read", ); controller.error( providerError(error, provider, params.abortSignal?.aborted), ); } }, cancel(reason) { finish("aborted"); return reader.cancel(reason); }, }); return { stream }; } catch (error) { finish( params.abortSignal?.aborted || (error instanceof Error && error.name === "AbortError") ? "aborted" : "error", error, "request", ); throw providerError(error, provider, params.abortSignal?.aborted); } }, }, });}