1/// <reference types="@cloudflare/workers-types" />
2
3import * as Data from "effect/Data";
4import type * as Effect from "effect/Effect";
5import type * as Layer from "effect/Layer";
6import type { LanguageModel } from "effect/ai/LanguageModel";
7import type { RuntimeContext } from "../../RuntimeContext.ts";
8import type { LanguageModelOptions } from "../AI/LanguageModel.ts";
9import type { AIBinding } from "./AIBinding.ts";
10import * as Binding from "./Binding.ts";
11
12const TypeId = "Cloudflare.Workers.AI" as const;
13type TypeId = typeof TypeId;
14
15/**
16 * Error raised by Workers AI runtime operations (`ai.run`, `ai.models`, …).
17 */
18export class WorkersAIError extends Data.TaggedError("WorkersAIError")<{
19 /**
20 * Human-readable runtime error message.
21 */
22 message: string;
23 /**
24 * Original error thrown by the Cloudflare runtime binding.
25 */
26 cause: unknown;
27}> {}
28
29/**
30 * The native Cloudflare Workers AI binding — run inference on Workers AI
31 * models directly from a Worker, with no AI Gateway (or any other cloud
32 * resource) required. This is the plain `{ type: "ai" }` Worker binding: the
33 * runtime value is the same `env.AI` handle you would declare in
34 * `wrangler.json`.
35 *
36 * `AI` is a single value that is at once the `Binding.Service` tag, the
37 * callable that produces an {@link AIBinding}, and the type. Declare it on a
38 * Worker's `env` (it flows through `InferEnv` → the runtime `Ai` handle) or
39 * `yield*` it inside an Effect-native Worker to attach the binding and obtain
40 * the {@link AIClient}.
41 *
42 * Use `Cloudflare.AI.Gateway` + `QueryGateway` instead when you want requests
43 * routed through an AI Gateway (caching, rate limiting, logs); use `AI` when
44 * you just want to call Workers AI models.
45 *
46 *
47 * ### Effect-style Worker (recommended)
48 * **Example:** Run a Workers AI model
49 * ```typescript
50 * Cloudflare.Worker("AiWorker", { main: import.meta.url },
51 * Effect.gen(function* () {
52 * const ai = yield* Cloudflare.Workers.AI();
53 * return {
54 * fetch: Effect.gen(function* () {
55 * const result = yield* ai.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
56 * prompt: "What is the origin of the phrase Hello, World?",
57 * }).pipe(Effect.orDie);
58 * return yield* HttpServerResponse.json(result);
59 * }),
60 * };
61 * }).pipe(Effect.provide(Cloudflare.Workers.AIBinding)),
62 * );
63 * ```
64 *
65 * ### Effect AI LanguageModel
66 * **Example:** `ai.model(...)` -> Effect AI `LanguageModel`
67 * `model(options)` produces a `Layer<LanguageModel, never, RuntimeContext>`
68 * that translates `LanguageModel.generateText` / `streamText` calls
69 * (including tool calls) into `ai.run(...)` against the bound Workers AI
70 * model — the same adapter AI Gateway's `QueryGateway` uses, minus the
71 * gateway routing.
72 * ```typescript
73 * const ai = yield* Cloudflare.Workers.AI();
74 *
75 * const languageModel = ai.model({
76 * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
77 * parameters: { temperature: 0.7, maxTokens: 1024 },
78 * });
79 *
80 * const response = yield* LanguageModel.generateText({ prompt }).pipe(
81 * Effect.provide(languageModel),
82 * );
83 * ```
84 *
85 * ### Binding to an Async Worker
86 * **Example:** Example
87 * ```typescript
88 * export const Worker = Cloudflare.Worker("Worker", {
89 * main: "./src/worker.ts",
90 * env: { AI: Cloudflare.Workers.AI() },
91 * });
92 *
93 * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
94 * // { AI: Ai }
95 * ```
96 *
97 * @see https://developers.cloudflare.com/workers-ai/
98 *
99 * @binding
100 * @product Workers AI
101 * @category AI
102 */
103export interface AI extends Binding.Service<AI, TypeId, AIClient> {
104 /**
105 * @param name Binding name (logical id) — the `env` key it resolves to.
106 * @default "AI"
107 */
108 (name?: string): AIBinding;
109}
110
111export const AI = Binding.Service<AI>({
112 id: TypeId,
113 defaultName: "AI",
114 toWorkerBinding: (binding) => ({ type: "ai", name: binding.name }),
115});
116
117export const isAI = (value: unknown): value is AIBinding =>
118 Binding.isBinding(value) && value.kind === TypeId;
119
120/**
121 * Effect-native client for a Cloudflare Workers AI binding. Wraps the runtime
122 * `Ai` handle so each operation returns an Effect tagged with
123 * {@link WorkersAIError}, and provides a `model(options)` factory that
124 * produces an `effect/ai` `LanguageModel` `Layer`.
125 */
126export interface AIClient {
127 /**
128 * Effect resolving to the raw Workers AI runtime binding.
129 */
130 raw: Effect.Effect<Ai, never, RuntimeContext>;
131 /**
132 * Run inference on a Workers AI model. Typed by the model catalog from
133 * `@cloudflare/workers-types` — pass `options` (e.g. `returnRawResponse`,
134 * `gateway`) through to the runtime binding.
135 */
136 run<Name extends keyof AiModels>(
137 model: Name,
138 inputs: AiModels[Name]["inputs"],
139 options?: AiOptions,
140 ): Effect.Effect<
141 AiModels[Name]["postProcessedOutputs"],
142 WorkersAIError,
143 RuntimeContext
144 >;
145 /**
146 * List Workers AI models from the catalog, optionally filtered.
147 */
148 models(
149 params?: AiModelsSearchParams,
150 ): Effect.Effect<AiModelsSearchObject[], WorkersAIError, RuntimeContext>;
151 /**
152 * Provide an `effect/ai` `LanguageModel` layer backed by this
153 * binding and the given Workers AI model.
154 */
155 model(
156 options: Omit<LanguageModelOptions, "client">,
157 ): Layer.Layer<LanguageModel, never, RuntimeContext>;
158}