1import * as Cloudflare from "@/Cloudflare/index.ts";
2import type { RuntimeContext } from "@/RuntimeContext.ts";
3import * as Effect from "effect/Effect";
4import * as Stream from "effect/Stream";
5import { LanguageModel as AiLanguageModel } from "effect/ai";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8
9// `@cf/meta/llama-3.1-8b-instruct` was deprecated by Cloudflare on
10// 2026-05-30 (the API answers 410), so use the supported fast 3.3 model.
11const MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
12
13export default class AiBindingTestWorker extends Cloudflare.Worker<AiBindingTestWorker>()(
14 "AiBindingTestWorker",
15 {
16 main: import.meta.url,
17 },
18 Effect.gen(function* () {
19 const ai = yield* Cloudflare.Workers.AI();
20
21 const languageModel = ai.model({
22 model: MODEL,
23 parameters: { temperature: 0.7, maxTokens: 1024 },
24 });
25
26 return {
27 fetch: Effect.gen(function* () {
28 const request = yield* HttpServerRequest;
29 const ctx = yield* Effect.context<RuntimeContext>();
30 const url = new URL(request.url, "http://worker");
31 const prompt =
32 url.searchParams.get("prompt") ??
33 "Say the single word 'pong' and nothing else.";
34
35 if (url.pathname === "/run") {
36 // Raw `ai.run` against the binding — no gateway involved.
37 const result = yield* ai
38 .run(MODEL, {
39 messages: [{ role: "user", content: prompt }],
40 max_tokens: 128,
41 })
42 .pipe(Effect.orDie);
43 return yield* HttpServerResponse.json(result);
44 }
45
46 if (url.pathname === "/models") {
47 const models = yield* ai
48 .models({ search: "llama-3.3" })
49 .pipe(Effect.orDie);
50 return yield* HttpServerResponse.json({
51 count: models.length,
52 names: models.map((m) => m.name),
53 });
54 }
55
56 if (url.pathname === "/generate") {
57 const response = yield* AiLanguageModel.generateText({ prompt }).pipe(
58 Effect.orDie,
59 );
60 return yield* HttpServerResponse.json({
61 text: response.text,
62 finishReason: response.finishReason,
63 usage: {
64 inputTokens: response.usage.inputTokens.total,
65 outputTokens: response.usage.outputTokens.total,
66 },
67 });
68 }
69
70 if (url.pathname === "/stream") {
71 const encoder = new TextEncoder();
72 const body = AiLanguageModel.streamText({ prompt }).pipe(
73 Stream.map((part) =>
74 encoder.encode(`data: ${JSON.stringify(part)}\n\n`),
75 ),
76 Stream.provide(languageModel),
77 Stream.provideContext(ctx),
78 );
79 return HttpServerResponse.stream(body, {
80 headers: { "content-type": "text/event-stream" },
81 });
82 }
83
84 return HttpServerResponse.text("ok");
85 }).pipe(Effect.provide(languageModel)),
86 };
87 }).pipe(Effect.provide(Cloudflare.Workers.AIBinding)),
88) {}