import { afterEach, expect, test } from "bun:test"; import { AIError, createAI } from "../src/index.ts"; const originalFetch = globalThis.fetch; afterEach(() => { globalThis.fetch = originalFetch; }); test("requires an API key before making a request", async () => { const previous = process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY; await expect(createAI().generate("hello")).rejects.toBeInstanceOf(AIError); if (previous !== undefined) process.env.ANTHROPIC_API_KEY = previous; }); test("builds a bounded messages request and joins text blocks", async () => { let request: RequestInit | undefined; globalThis.fetch = (async (_url, init) => { request = init; return Response.json({ content: [{ type: "text", text: "Hello" }, { type: "tool_use" }] }); }) as typeof fetch; const text = await createAI({ apiKey: "secret", model: "test-model", maxTokens: 123 }).generate( "Hi", ); expect(text).toBe("Hello"); expect(JSON.parse(String(request?.body))).toMatchObject({ model: "test-model", max_tokens: 123, messages: [{ role: "user", content: "Hi" }], stream: false, }); }); test("streams fragmented SSE and keeps a final event without a newline", async () => { const encoder = new TextEncoder(); globalThis.fetch = (async () => new Response( new ReadableStream({ start(controller) { controller.enqueue( encoder.encode('data: {"type":"content_block_delta","delta":{"type":"text_'), ); controller.enqueue(encoder.encode('delta","text":"A"}}\n')); controller.enqueue( encoder.encode( 'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"B"}}', ), ); controller.close(); }, }), )) as unknown as typeof fetch; const chunks: string[] = []; for await (const chunk of createAI({ apiKey: "secret" }).stream("Hi")) chunks.push(chunk); expect(chunks).toEqual(["A", "B"]); });