These libraries are experimental. APIs may change without notice. Generated from source with koruc 0.1.7 on 8/19/2026.
AI
@korulang/ai@0.0.1First-class Koru edition of LLM text generation (Anthropic, OpenAI) over @korulang/curl, with request + response resources tracked as phantom obligations
ai/index.kz · 5 tors
@koru/ai - AI SDK for Koru · 29 more lines
@koru/ai - AI SDK for Koru
A first-class Koru edition of LLM text generation over the Anthropic and
OpenAI HTTP APIs. Built on @koru/curl — dogfooding our own lifted HTTP
client. The lift is entirely in the type system: a request-in-flight carries
a `built!` free-obligation and the HTTP response carries curl's `open!`
close-obligation, so the compiler will not let a caller leak the request
scratch OR forget to close the connection. Two obligations, both enforced,
discharged in the caller's flow. Non-streaming: one request, one reply.
── Shape & history (why it looks like this) ─────────────────────────────────
The pre-current-language edition of this file (a) declared bare `~proc`s
(KORU110: bare procs are unresolvable in the current language) and (b) called
curl through an INVENTED Zig escape hatch —
`@import("root").koru_libs.curl.post_with_headers_event.call(.{...})` — a
symbol attested by no passing test. This edition removes both. Composition
with curl now uses the sanctioned koru-level, cross-module event form
(`koru/curl:post.with-headers(...)`, `koru/curl:close(...)`) in flow position,
grounded in tests/regression/100_MODULE_SYSTEM/110_IMPORTS/
110_020_cross_module_literal_arg and koru-by-example's subflow section.
The API is a set of building-block events the caller wires in a top-level
flow (see examples/ and README.md), NOT a single `generate(...)` convenience
event. That is a deliberate, forced choice: a single all-in-one event would
have to be a subflow that borrow-parses the response, discharges it, and
produces a value — and that exact shape is blocked by three current compiler
gaps (see LIFT_NOTES.md "Toolchain findings"). The building-block flow sits
entirely in top-level flow position, which the compiler handles cleanly and
which — bonus — makes BOTH obligations visible at the call site.
Phantom lifecycles
Derived from the phantom labels in the declarations below — state! issues an
obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.
Request 1 state built!Response is borrowed: this module holds <koru/curl:open> without issuing or discharging it — the obligation belongs to
koru/curl.
// Request builders — provider-specific JSON, produced as a `built!` obligation
~pub tor request.anthropic {
model: string,
prompt: string,
system: ?string,
max_tokens: ?i32,
allocator: ?std.mem.Allocator
} -> *Request<built!>~pub tor request.openai {
model: string,
prompt: string,
system: ?string,
max_tokens: ?i32,
allocator: ?std.mem.Allocator
} -> *Request<built!>// Discharge the request's `built!` obligation. Frees the JSON body and the
// Request struct. Omit this call in a flow and the build fails (KORU030).
~pub tor free.request { req: *Request<!built> }// Response parsers — BORROW the curl response, produce an OWNED result
// These take the response as `<koru/curl:open>` (a borrow, never a consume):
// the caller still owes the `koru/curl:close`. They dupe every returned byte
// out of the response body, so the result survives that close.
~pub tor parse.anthropic { resp: koru/curl:*Response<koru/curl:open> }
| ok GenerateResult
| err Error~pub tor parse.openai { resp: koru/curl:*Response<koru/curl:open> }
| ok GenerateResult
| err Error