import { Effect, Schema, Stream } from "effect" import { ai, type Finish, type LanguageModel, type LanguageModelProviderOptions, type LLMClientService, type ProviderOptions, } from "../src/effect.js" import { AI } from "../src/index.js" import { OpenAIChat } from "../src/protocols.js" interface ExampleOptions { readonly [key: string]: unknown readonly mode?: "fast" | "thorough" } type ExampleProviderOptions = ProviderOptions & ExampleOptions const model = OpenAIChat.route .with({ endpoint: { baseURL: "https://example.com/v1" } }) .model({ id: "example" }) type Requirements = T extends Effect.Effect ? R : never type Success = T extends Effect.Effect ? A : never type StreamEvent = T extends Stream.Stream ? A : never type StreamRequirements = T extends Stream.Stream ? R : never type Equal = [A, B] extends [B, A] ? true : false type Assert = T ai.request({ model, prompt: "Hello", providerOptions: { mode: "fast" } }) ai.request({ model, prompt: "Hello", providerOptions: { future: { option: true } } }) const generated = ai.generate(ai.request({ model, prompt: "Hello" })) type GenerateRequirements = Assert, LLMClientService>> const streamed = ai.stream(ai.request({ model, prompt: "Hello" })) type StreamClientRequirements = Assert, LLMClientService>> const generatedFromInput = ai.generate({ model, prompt: "Hello", providerOptions: { mode: "fast" } }) type InputGenerateRequirements = Assert, LLMClientService>> const streamedFromInput = ai.stream({ model, prompt: "Hello", providerOptions: { mode: "thorough" } }) type InputStreamRequirements = Assert, LLMClientService>> ai.request({ model, prompt: "Hello", // @ts-expect-error Known provider options preserve their value types. providerOptions: { mode: "slow" }, }) // @ts-expect-error Direct input keeps the selected model's provider option types. ai.generate({ model, prompt: "Hello", providerOptions: { mode: "slow" } }) // @ts-expect-error Stream input keeps the selected model's provider option types. ai.stream({ model, prompt: "Hello", providerOptions: { mode: "slow" } }) const generatedObject = ai.generate({ model, prompt: "Hello", schema: Schema.Struct({ answer: Schema.String }), providerOptions: { mode: "thorough" }, }) type StructuredOutputRequirements = Assert, LLMClientService>> type StructuredOutputValue = Assert["object"], { readonly answer: string }>> type StructuredOutputText = Assert["text"], string>> const generatedDynamicObject = ai.generate({ model, prompt: "Hello", schema: { type: "object" }, }) type GenerateDynamicObjectRequirements = Assert, LLMClientService>> type GenerateDynamicObjectValue = Assert["object"], unknown>> // @ts-expect-error Structured output keeps the selected model's provider options. ai.generate({ model, prompt: "Hello", schema: { type: "object" }, providerOptions: { mode: false } }) // @ts-expect-error A plain request has no decoded object. void ai.generate({ model, prompt: "Hello" }).pipe(Effect.map((response) => response.object)) type FinishObject = Extract extends { readonly object?: infer O } ? O : never const streamedObject = ai.stream({ model, prompt: "Hello", schema: Schema.Struct({ answer: Schema.String }) }) type StreamObjectValue = Assert>, { readonly answer: string }>> const loopedObject = ai.loop({ model, prompt: "Hello", tools: [], schema: Schema.Struct({ answer: Schema.String }) }) type LoopObjectValue = Assert< Equal< Extract, { readonly type: "finish" }>, Finish & { readonly object?: { readonly answer: string } } > > declare const generic: LanguageModel ai.request({ model: generic, prompt: "Hello", providerOptions: { arbitrary: { option: true } } }) const options: LanguageModelProviderOptions = { mode: "fast" } void (options satisfies LanguageModelProviderOptions) void (true satisfies GenerateRequirements) void (true satisfies StreamClientRequirements) void (true satisfies InputGenerateRequirements) void (true satisfies InputStreamRequirements) void (true satisfies StructuredOutputRequirements) void (true satisfies GenerateDynamicObjectRequirements) void (true satisfies StructuredOutputValue) void (true satisfies StructuredOutputText) void (true satisfies GenerateDynamicObjectValue) void (true satisfies StreamObjectValue) void (true satisfies LoopObjectValue) void AI.ai .generate({ model, prompt: "Hello", schema: Schema.Struct({ answer: Schema.String }) }) .then((response) => response.object.answer satisfies string) void AI.ai.generate({ model, prompt: "Hello", providerOptions: { mode: "fast" } }) void AI.ai.stream({ model, prompt: "Hello", providerOptions: { mode: "thorough" } }) void AI.ai.generate(AI.ai.request({ model, prompt: "Hello" })) void AI.ai.stream(AI.ai.request({ model, prompt: "Hello" })) // @ts-expect-error Promise direct input keeps the selected model's provider option types. void AI.ai.generate({ model, prompt: "Hello", providerOptions: { mode: "slow" } }) // @ts-expect-error Promise stream input keeps the selected model's provider option types. void AI.ai.stream({ model, prompt: "Hello", providerOptions: { mode: "slow" } }) ai.request({ model, system: "Be brief.", prompt: "Hello" }) ai.request({ model, system: "Be brief.", messages: [] }) // @ts-expect-error A request needs `prompt` or `messages`. ai.request({ model }) // @ts-expect-error A request takes `prompt` or `messages`, not both. ai.request({ model, prompt: "Hello", messages: [] }) // @ts-expect-error Effect generate needs `prompt` or `messages`. ai.generate({ model, system: "Be brief." }) // @ts-expect-error Effect stream takes `prompt` or `messages`, not both. ai.stream({ model, prompt: "Hello", messages: [] }) // @ts-expect-error Effect loop needs `prompt` or `messages`. ai.loop({ model, tools: [] }) // @ts-expect-error Effect structured output takes `prompt` or `messages`, not both. ai.generate({ model, prompt: "Hello", messages: [], schema: Schema.String }) // @ts-expect-error Promise generate needs `prompt` or `messages`. void AI.ai.generate({ model }) // @ts-expect-error Promise stream takes `prompt` or `messages`, not both. AI.ai.stream({ model, prompt: "Hello", messages: [] }) // @ts-expect-error Promise loop takes `prompt` or `messages`, not both. AI.ai.loop({ model, prompt: "Hello", messages: [], tools: [] }) AI.ai.loop({ model, messages: [], tools: [] }) // Transport fields sit at the top level; `http` keeps only the rare wire overrides. ai.request({ model, prompt: "Hello", headers: { "x-trace": "1" }, timeout: 30_000, fetch: globalThis.fetch }) ai.request({ model, prompt: "Hello", timeout: { total: false, headers: 1_000, chunk: 2_000 } }) ai.request({ model, prompt: "Hello", http: { query: { trace: "1" }, body: { extra: true }, encoding: "gzip" } }) // @ts-expect-error Headers moved out of `http`. ai.request({ model, prompt: "Hello", http: { headers: { "x-trace": "1" } } }) // @ts-expect-error Timeouts moved out of `http`. ai.request({ model, prompt: "Hello", http: { headerTimeout: 1_000 } }) // Portable knobs sit at the top level; editing one is a flat spread. const knobs = ai.request({ model, prompt: "Hello", maxTokens: 256, temperature: 0, stop: ["END"] }) ai.generate({ ...knobs, maxTokens: 512 }) // @ts-expect-error The nested `generation` object was removed. ai.request({ model, prompt: "Hello", generation: { maxTokens: 256 } })