ai
v0.1.0 · compiled with katari 0.1.0
Overview
from the package READMEai — a provider-agnostic AI tool-calling loop for Katari
The tool-calling loop, factored so the model provider is one use line. The whole abstraction is a
single request — ai.infer_step — so the loop never names a provider; adding one means adding a
module, not editing the loop.
Modules
ai— the loop.ai.infer_with_tools(history, tools, max_steps)runs the model until it stops calling tools, dispatching each tool batch concurrently (parallel for) and validating every call against its tool's schema (reflection.call_agent). Also exposesai.view_image(a tool that inlines an image for the model to look at).ai.types— the provider-agnostic vocabulary:turn,tool_call,tool_result,step. Apps and providers speak only this; no wire-format detail leaks in.ai.gemini— a provider over Google'sgenerateContentAPI.use gemini.provider(model = ..., api_key = ...)servesai.infer_stepfor the continuation. Images: yes — a turn's image files inline asinlineDataparts (freshest turn only, to keep the request small).ai.openai— a provider over the OpenAI responses API.use openai.provider(model = ..., api_key = ...)— swap it forgemini.provider(or vice versa) with no other change. Images: no — a turn's files appear to the model as handle notes only.ai.anthropic— a provider over the Anthropic Messages API.use anthropic.provider(api_key = ...)—modeldefaults toclaude-sonnet-5andmax_tokens(the per-step output cap the API requires) to 4096; swap it for either other provider with no other change. Images: yes — a turn's image files inline asimagecontent blocks.
Pure Katari — no FFI sidecar. The only network call is the prelude's HTTP client: the Gemini and
Anthropic providers post an http.json value tree with http.fetch (so a turn's image file leaves
base64 at the send boundary, never on the value plane), OpenAI a plain-body http.post_json; every
request body is built and every response parsed as json values in Katari.
Secrets / env
- A model API key, provided to whichever provider you
use:- Gemini:
GEMINI_API_KEY→use gemini.provider(api_key = env.get_secret(key = "GEMINI_API_KEY")) - OpenAI:
OPENAI_API_KEY→use openai.provider(api_key = env.get_secret(key = "OPENAI_API_KEY")) - Anthropic:
ANTHROPIC_API_KEY→use anthropic.provider(api_key = env.get_secret(key = "ANTHROPIC_API_KEY"))
- Gemini:
Store it in the runtime, never in a file: katari env set GEMINI_API_KEY --secret. The key is a
string of private; it can flow into the provider's auth header but never out to a user boundary.
Usage
import ai
import ai.types
import ai.gemini
agent solve(task: string) -> string with io {
use gemini.provider(
model = "gemini-3.5-flash",
api_key = env.get_secret(key = "GEMINI_API_KEY"),
system = "You are a helpful assistant.",
)
ai.infer_with_tools(
history = [types.turn(role = "user", text = task, files = [])],
tools = [ai.view_image], // add your own tools here
max_steps = 12,
)
}Imported modules are referenced by their last path segment: ai.*, types.*, gemini.provider.
ai
25 declarationsinfer_step#
requestrequest infer_step(history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.stepOne model step over the conversation + tool schemas — THE provider seam. A provider module serves this request for the extent of a continuation (use gemini.provider(...)), so the loop stays provider-agnostic and providers stay loop-agnostic.
Parameters
Returns
Wire view (JSON Schema)
step_error#
typetype step_error = json.parse_error | http.status_error | http.fetch_errorDefinition
loop_error#
typetype loop_error = reflection.call_errorDefinition
reply#
agentagent reply(history: array[types.message]) -> stringInfer a single reply with no tools (the common chat case).
Parameters
Returns
Inferred type
agent(history: array[call_turn | result_turn | turn]) -> string with infer_step
Wire view (JSON Schema)
view_image#
agentagent view_image(image: file) -> fileTool: load an image file into your visual context. Pass the file's handle object — a bare {"$katari_ref": "<id>"} suffices (copy the id from the conversation); the image content arrives with this tool's result, so you can see it on your next step.
Parameters
Returns
Wire view (JSON Schema)
infer_with_tools#
agentagent infer_with_tools[effect E](history: array[types.message], tools: array[agent never -> unknown with E], max_steps: integer) -> stringRun the tool-using model loop, generic over the tools' shared effect E: derive each tool's schema with reflection.get_metadata, then up to max_steps times perform infer_step against the ambient provider; on a batch of tool calls, dispatch them all CONCURRENTLY with parallel for (each resolves its tool by name and invokes it by value via reflection.call_agent, validating the model's args against the tool's schema), then feed every result back as a new turn; on a final answer, return it.
Generics
Parameters
Returns
Inferred type
agent(history: array[call_turn | result_turn | turn], max_steps: integer, tools: array[agent(...never) -> unknown with E0]) -> string with infer_step | throw[call_error] | E0
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
dispatch_one#
agentagent dispatch_one[effect E](tools: array[agent never -> unknown with E], call: types.tool_call) -> types.tool_resultFind the tool whose metadata name is the model's pick and invoke it by value via reflection.call_agent (the model's args ride as a plain value, and the delegation boundary validates them against the tool's schema — the compiled signature of a Katari agent, or the server-declared schema of an MCP tool — while the boundary's unconditional wire→value conversion turns any replayed $katari_ref into the file it denotes). A hallucinated name or a panicking tool is fed back as an error result so the model corrects on the next step; genuinely malformed args are a reflection.call_error that escalates to the app root (loop_error). The result's file values are collected off the result tree so the provider can show them to the model (their $katari_ref handles are already visible in the result text).
Generics
Parameters
Returns
Inferred type
agent(call: tool_call, tools: array[agent(...never) -> unknown with E1]) -> tool_result with throw[call_error] | E1
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
collect_result_files#
agentagent collect_result_files(node: unknown) -> array[file] with pureCollect every file value out of a tool result's value tree: reflection.call_agent returns the tool's result as a plain value, so a file the tool produced (a generated image, an MCP tool's screenshot) is an actual file leaf — found wherever it sits (a bare return, the MCP bridge's { text, files } record, or nested structure).
Parameters
Returns
Effects
Inferred type
agent(node: unknown) -> array[file]
Wire view (JSON Schema)
ai_turn#
effecteffect ai_turnThe infer_with_region nursery scope: a nullary phantom marker carried by every monitor fiber, minted and discharged by the loop's region.provide so no fiber outlives the loop.
observation#
requestrequest observation(source: string, content: string, files: array[file]) -> stringTHE monitor→model channel: a background monitor reports content (plus any files attached to it) from source and receives the model's answer back. It is the only effect a monitor raises besides io, which pins the nursery's ceiling to one row so a single region.watch covers every monitor. A source fiber or a started monitor performs it; the loop's own handler answers it by running a model turn over the injected report. files ride the injection turn's files slot — the same text + files split as types.turn — so the provider's existing inline policy shows the model any attached image.
Parameters
Which monitor is reporting (e.g. "slack").
The report to inject into the model's context.
Any files attached to the report (a Discord / Slack attachment), injected on the report's turn so the provider inlines them for the model.
Returns
Wire view (JSON Schema)
region_session#
datadata region_session(conversation: array[types.message], registry: record[region.fiber[ai_turn, never]])The loop's durable state, threaded across every observation: the running conversation and the registry of started monitors (a stable id → the fiber handle region.cancel needs). The id is what the model holds and passes back to stop a monitor.
Parameters
Wire view (JSON Schema)
region_turn_result#
datadata region_turn_result(reply: string, session: region_session)One model turn's outcome: the reply handed back to the reporting fiber, plus the advanced session the loop persists for the next observation.
Parameters
Wire view (JSON Schema)
region_step_result#
datadata region_step_result(result: types.tool_result, registry: record[region.fiber[ai_turn, never]])One routed tool call: the model-facing result plus the possibly-grown monitor registry (a started monitor adds its id → fiber; a stop or a synchronous tool leaves it be).
Parameters
Wire view (JSON Schema)
region_batch#
datadata region_batch(registry: record[region.fiber[ai_turn, never]], results: array[types.tool_result])One routed batch: the registry after the whole batch plus the results in call order — one var so the batch fold threads both at once.
Parameters
Wire view (JSON Schema)
cancel_monitor#
agentagent cancel_monitor(monitor_id: string) -> stringTool: STOP a background monitor you started earlier. Pass the monitor id you were given when it started. The region loop INTERCEPTS this call (only the loop holds the nursery region.cancel needs), so this body is the schema seam and never runs; it returns a note so a stray direct call stays total.
Parameters
The id you were given when the monitor started.
Returns
Wire view (JSON Schema)
route_region_call#
agentagent route_region_call[effect E](nursery: region.nursery[ai_turn, {...E, observation} | io], tools: array[agent never -> unknown with E], monitors: array[agent(input: null) -> never with {...E, observation} | io], registry: record[region.fiber[ai_turn, never]], call: types.tool_call) -> region_step_resultRoute one tool call inside the region loop: STOP a monitor (cancel_monitor — cancel the fiber the model's id names), START a monitor (a name in monitors — region.fork it detached and hand the model its id), or run a SYNCHRONOUS tool (anything else — dispatch_one, exactly as infer_with_tools). Returns the model-facing result with the possibly-grown registry.
Generics
Parameters
The nursery to fork / cancel monitors in.
The synchronous tools (called and awaited).
The startable monitors (forked detached).
Returns
Inferred type
agent(call: tool_call, monitors: array[agent(input: null) -> never with observation | E2 | io], nursery: agent(bound: agent(...never) -> unknown with observation | E2 | io, gate: agent(...never) -> unknown with ai_turn) -> {bound: agent(...never) -> unknown with observation | E2 | io, gate: agent(...never) -> unknown with ai_turn}, registry: record[agent(...never) -> never with ai_turn], tools: array[agent(...never) -> unknown with E2]) -> region_step_result with ai_turn | throw[call_error] | E2 | ioWire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
region_turn_loop#
agentagent region_turn_loop[effect E](nursery: region.nursery[ai_turn, {...E, observation} | io], conversation: array[types.message], tools: array[agent never -> unknown with E], monitors: array[agent(input: null) -> never with {...E, observation} | io], registry: record[region.fiber[ai_turn, never]], max_steps: integer) -> region_turn_resultOne model turn-loop over conversation WITH region powers — the region-aware twin of the infer_with_tools loop. Up to max_steps times: ask the provider for a step; on a tool batch, route every call (route_region_call: a synchronous tool feeds its result back, a monitor tool forks a detached fiber whose id feeds back, cancel_monitor stops one), record the call + result turns, and continue; on a final answer, stop. Budget spent, force one tool-less close (as infer_with_tools). Returns the reply plus the advanced session (conversation + monitor registry).
Generics
Parameters
The nursery to fork / cancel monitors in.
The synchronous tools.
The startable monitors.
The per-turn step budget.
Returns
Inferred type
agent(conversation: array[call_turn | result_turn | turn], max_steps: integer, monitors: array[agent(input: null) -> never with observation | E3 | io], nursery: agent(bound: agent(...never) -> unknown with observation | E3 | io, gate: agent(...never) -> unknown with ai_turn) -> {bound: agent(...never) -> unknown with observation | E3 | io, gate: agent(...never) -> unknown with ai_turn}, registry: record[agent(...never) -> never with ai_turn], tools: array[agent(...never) -> unknown with E3]) -> region_turn_result with ai_turn | infer_step | throw[call_error] | E3 | ioWire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
infer_with_region#
agentagent infer_with_region[effect E](context: array[types.message], tools: array[agent never -> unknown with E], monitors: array[agent(input: null) -> never with {...E, observation} | io], sources: array[agent(input: null) -> never with {...E, observation} | io], max_steps: integer, region_instructions: string ?= "You have background MONITOR tools in addition to ordinary tools. An ordinary tool returns its result immediately. Calling a MONITOR tool instead STARTS a background watcher and returns a monitor id; that watcher keeps running on its own and notifies you later, each notification arriving as a 'Notification from <source>' message. To STOP a monitor, call cancel_monitor with the id you were given. Start a monitor when the user asks you to keep an eye on something, and stop it when they ask you to stop.") -> neverRun the tool-using model loop over a region nursery, so the model can START background monitors and STOP them — the long-lived sibling of infer_with_tools. tools are synchronous (called and awaited, exactly as infer_with_tools); monitors are startable background fibers the model launches as tools (each forks detached and notifies the model through observation) and stops with cancel_monitor; sources are always-on input fibers forked at startup (e.g. a chat watcher that turns each user message into an observation) and are NOT shown as tools. context seeds the conversation the model always sees, and region_instructions (overridable) teaches the model the start / stop protocol, since a model is trained on tool calls but not on this fork / cancel discipline.
Returns never: structured concurrency cancels every fiber when the nursery closes, so to keep monitors alive the loop never returns — the model's replies leave through the observation channel (each is the answer handed to the fiber that reported), and the loop runs until the run is cancelled. The provider (use gemini.provider(...) …) and any E handlers are installed by the caller, exactly as for infer_with_tools.
Generics
Parameters
Synchronous tools: called and awaited, the result fed back.
Startable monitors: forked detached when the model calls one, they notify through observation.
Always-on input fibers forked at startup (not shown as tools).
The per-observation step budget.
How the model should use the monitor start / stop tools; override to specialise.
Returns
Inferred type
agent(context: array[call_turn | result_turn | turn], max_steps: integer, monitors: array[agent(input: null) -> never with observation | E4 | io], region_instructions?: string, sources: array[agent(input: null) -> never with observation | E4 | io], tools: array[agent(...never) -> unknown with E4]) -> never with infer_step | throw[call_error] | E4 | io
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
name_map#
datadata name_map(real_to_wire: record[string], wire_to_real: record[string])A deterministic two-way mapping between a tool's real (qualified) name and the shortened name a provider sends on the wire. real_to_wire is keyed by the qualified name (used when encoding a tool declaration or replaying a call turn), wire_to_real by the wire name (used to reverse the model's pick); both are total over one step's tools — see build_name_map.
Parameters
Qualified tool name → wire name.
Wire name → qualified tool name.
Wire view (JSON Schema)
sanitize_name#
agentagent sanitize_name(name: string) -> stringFold a wire name onto a qualified name: every character outside a provider's [a-zA-Z0-9_-] name alphabet (the dot of a qualified name, above all) folds to _, then the result is capped at the providers' 64-character limit.
Parameters
The qualified tool name.
Returns
Wire view (JSON Schema)
suffixed_name#
agentagent suffixed_name(base: string, index: integer) -> stringDisambiguate a colliding wire name with an ordinal suffix (_2, _3, …), reserving room so the result still fits the 64-character cap.
Parameters
The colliding base wire name.
The 1-based ordinal.
Returns
Wire view (JSON Schema)
assign_wire_name#
agentagent assign_wire_name(taken: record[string], real: string) -> stringChoose a free wire name for real given the wire names already taken: its sanitized form, or — when sanitizing collapsed two distinct qualified names onto one token, or truncation shared a prefix — the first free ordinal-suffixed form. The suffix is decided by taken's size (processing order), so the choice is deterministic.
Parameters
The wire names already assigned this step.
The qualified name to place.
Returns
Inferred type
agent(real: string, taken: record[string]) -> string
Wire view (JSON Schema)
build_name_map#
agentagent build_name_map(tool_metas: array[reflection.agent_metadata]) -> name_mapBuild the deterministic wire-name name_map for one step's tools. Walking tool_metas in order, each tool's qualified name is assigned a free wire name against the names already placed, so the same tool list always yields the same map — which is why the map needs no durable state (see the section note). A provider whose wire vocabulary admits the qualified name (Gemini) never calls this.
Parameters
Returns
Inferred type
agent(tool_metas: array[agent_metadata]) -> name_map
Wire view (JSON Schema)
wire_name_of#
agentagent wire_name_of(map: name_map, real: string) -> stringThe wire name for a qualified tool name — used to encode a tool declaration and to re-encode a replayed call turn. Falls back to the qualified name for a tool absent from the map (never happens for a map built from the same step's tools).
Parameters
The qualified tool name.
Returns
Wire view (JSON Schema)
real_name_of#
agentagent real_name_of(map: name_map, wire: string) -> stringThe qualified tool name for a wire name — used to reverse the model's pick so the loop dispatches by the real name. Falls back to the wire name itself for an unknown name (a hallucinated tool), which the loop then reports as an unknown tool.
Parameters
The wire name the model returned.
Returns
Wire view (JSON Schema)
ai.anthropic
19 declarationsconnection#
datadata connection(model: string, api_key: string of private, system: string, max_tokens: integer)An Anthropic connection: the model id, the API key, an optional system prompt, and the output-token cap the Messages API requires on every request. Provider handler state — built once per use anthropic.provider(...).
Parameters
The Claude model id.
The API key, sent as the x-api-key header.
The system prompt; empty sends none.
The required max_tokens: the output-token cap of one step.
Wire view (JSON Schema)
provider#
agentagent provider[R, effect E](model: string ?= "claude-sonnet-5", api_key: string of private, system: string ?= "", max_tokens: integer ?= 4096, continuation: agent(value: null) -> R with {...E, ai.infer_step}) -> R with E | io | prelude.throw[ai.step_error]Serve ai.infer_step over Anthropic for the extent of the continuation: use anthropic.provider(api_key = env.get_secret(key = "ANTHROPIC_API_KEY")). A step's provider failure (ai.step_error) is raised here, at the provider's use site.
Generics
Parameters
The Claude model id.
The API key, sent as the x-api-key header.
An optional system prompt, sent as the top-level system field.
The output-token cap of one step; the Messages API requires one on every request.
The computation to run with ai.infer_step served.
Returns
Effects
Inferred type
agent(api_key: string of private, continuation: agent(value: null) -> T0 with infer_step | E1, max_tokens?: integer, model?: string, system?: string) -> T0 with throw[fetch_error | status_error | parse_error] | E1 | io
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
anthropic_role#
agentagent anthropic_role(role: string) -> stringMap the vocabulary role onto the Messages API role: our "model" speaks as "assistant".
Parameters
The ai.types turn role ("user" / "model").
Returns
Wire view (JSON Schema)
anthropic_file_notes#
agentagent anthropic_file_notes(files: array[file]) -> stringEvery file of a turn as a handle note, joined for the turn's text. The $katari_ref handle is the exact object the model replays into a tool call (view_image to re-see it, send_files to post it), so the note is the replay affordance — kept for an image too, alongside the image block that lets the model SEE it (see anthropic_turn_content).
Parameters
The files attached to a turn.
Returns
Wire view (JSON Schema)
anthropic_content_type#
agentagent anthropic_content_type(attachment: file) -> stringThe file's recorded content type, or "" when the handle is gone (freed, or never named a live blob). A gone file cannot inline — its handle note still rides — so gone folds to the same "not an image" signal as an unrecorded type, and never fails a whole step over one stale attachment.
Parameters
The file to read.
Returns
Wire view (JSON Schema)
anthropic_is_image#
agentagent anthropic_is_image(attachment: file) -> booleanWhether a file inlines as an Anthropic image block: its recorded content type begins "image/" (the Messages API image block takes only image media types). A file with another or no recorded type stays a handle note.
Parameters
The file to test.
Returns
Wire view (JSON Schema)
anthropic_text_block#
agentagent anthropic_text_block(text: string) -> unknownA text content block — { "type": "text", "text": @text@ }, built as a raw value tree.
Parameters
The block's text.
Returns
Wire view (JSON Schema)
anthropic_image_block#
agentagent anthropic_image_block(attachment: file) -> unknownAn image content block: a base64 source whose media_type is the file's recorded type and whose data slot holds the file VALUE placed DIRECTLY into the tree. http.json materialises that slot — reading the bytes and base64-ing them in place — only at the send boundary, so the image content never rides the value plane, the durable call record or the trace.
Parameters
The image file to inline.
Returns
Wire view (JSON Schema)
anthropic_turn_content#
agentagent anthropic_turn_content(text: string, files: array[file]) -> unknownA turn's content: the plain string (text + every file's handle note) when the turn carries no image, or an array of one text block plus one image block per image file when it does. An image file inlines so the model can SEE it; a non-image file stays a handle note (the image block takes only images). The text block still carries every file's handle note, so the model can replay any of them into a tool.
Parameters
The turn's text.
The turn's files.
Returns
Inferred type
agent(files: array[file], text: string) -> unknown
Wire view (JSON Schema)
anthropic_call_block#
agentagent anthropic_call_block(call: types.tool_call, map: ai.name_map) -> unknownParameters
Returns
Inferred type
agent(call: tool_call, map: name_map) -> unknown
Wire view (JSON Schema)
anthropic_result_block#
agentagent anthropic_result_block(result: types.tool_result) -> unknownParameters
Returns
Inferred type
agent(result: tool_result) -> unknown
Wire view (JSON Schema)
anthropic_message#
agentagent anthropic_message(message: types.message, map: ai.name_map) -> unknownParameters
Returns
Inferred type
agent(map: name_map, message: call_turn | result_turn | turn) -> unknown
Wire view (JSON Schema)
anthropic_tool#
agentagent anthropic_tool(meta: reflection.agent_metadata, map: ai.name_map) -> unknownParameters
Returns
Inferred type
agent(map: name_map, meta: agent_metadata) -> unknown
Wire view (JSON Schema)
anthropic_request_body#
agentagent anthropic_request_body(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata], map: ai.name_map) -> unknownParameters
Returns
Inferred type
agent(client: connection, history: array[call_turn | result_turn | turn], map: name_map, tool_metas: array[agent_metadata]) -> unknown
Wire view (JSON Schema)
anthropic_blocks#
agentagent anthropic_blocks(parsed: unknown) -> array[unknown]Parameters
The parsed response document.
Returns
Wire view (JSON Schema)
anthropic_block_kind#
agentagent anthropic_block_kind(block: unknown) -> stringParameters
One response content block.
Returns
Wire view (JSON Schema)
anthropic_tool_call#
agentagent anthropic_tool_call(block: unknown, map: ai.name_map) -> types.tool_callParameters
A tool_use response block.
Returns
Inferred type
agent(block: unknown, map: name_map) -> tool_call
Wire view (JSON Schema)
anthropic_parse_step#
agentagent anthropic_parse_step(parsed: unknown, map: ai.name_map) -> types.stepParameters
The parsed response document.
Returns
Inferred type
agent(map: name_map, parsed: unknown) -> (step_call | step_final)
Wire view (JSON Schema)
anthropic_infer_step#
agentagent anthropic_infer_step(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.stepOne Anthropic model step over the history + tool schemas (the body built as a value tree and the reply parsed by post_json in Katari; only the POST leaves the runtime, and each image file in the body base64s at that send boundary via the http.json slot contract).
Parameters
Returns
Inferred type
agent(client: connection, history: array[call_turn | result_turn | turn], tool_metas: array[agent_metadata]) -> (step_call | step_final) with throw[fetch_error | status_error | parse_error] | io
Wire view (JSON Schema)
ai.gemini
21 declarationsconnection#
datadata connection(model: string, api_key: string of private, system: string)A Gemini connection: the model id, the API key, and an optional system instruction. Provider handler state — built once per use gemini.provider(...).
Parameters
Wire view (JSON Schema)
provider#
agentagent provider[R, effect E](model: string, api_key: string of private, system: string ?= "", continuation: agent(value: null) -> R with {...E, ai.infer_step}) -> R with E | io | prelude.throw[ai.step_error]Serve ai.infer_step over Gemini for the extent of the continuation: use gemini.provider(model = "gemini-3.5-flash", api_key = env.get_secret(key = "GEMINI_API_KEY")). A step's provider failure (malformed JSON, a non-2xx status, a transport failure — ai.step_error) is raised here, at the provider's use site.
Generics
Parameters
Returns
Effects
Inferred type
agent(api_key: string of private, continuation: agent(value: null) -> T0 with infer_step | E1, model: string, system?: string) -> T0 with throw[fetch_error | status_error | parse_error] | E1 | io
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
gemini_role#
agentagent gemini_role(role: string) -> stringParameters
Returns
Wire view (JSON Schema)
gemini_text_part#
agentagent gemini_text_part(text: string) -> unknownParameters
Returns
Wire view (JSON Schema)
gemini_call_part#
agentagent gemini_call_part(call: types.tool_call) -> unknownParameters
Returns
Inferred type
agent(call: tool_call) -> unknown
Wire view (JSON Schema)
gemini_result_part#
agentagent gemini_result_part(result: types.tool_result) -> unknownParameters
Returns
Inferred type
agent(result: tool_result) -> unknown
Wire view (JSON Schema)
gemini_content_type#
agentagent gemini_content_type(f: file) -> stringThe file's recorded content type, or "" when the handle is gone (freed, or never named a live blob). A gone file cannot inline — its handle note still rides — so gone folds to the same "cannot inline" signal as an unrecorded type, and never fails a whole step over one stale attachment.
Parameters
Returns
Wire view (JSON Schema)
gemini_inline_part#
agentagent gemini_inline_part(f: file) -> unknownA file's content as an inlineData part — what lets the model actually SEE an image. The data slot holds the file VALUE placed DIRECTLY into the tree; http.json reads its bytes and base64s them in place only at the send boundary, so the content never rides the value plane. Only the freshest history element inlines, which keeps the WIRE request small (see gemini_content's inline).
Parameters
Returns
Wire view (JSON Schema)
gemini_handle_part#
agentagent gemini_handle_part(f: file) -> unknownA file as its $katari_ref handle note — the resting representation. The handle is the exact object the model replays into a tool call (view_image to re-see it, send_files to post it), so the note IS the affordance, not just a description.
Parameters
Returns
Wire view (JSON Schema)
gemini_file_parts#
agentagent gemini_file_parts(files: array[file], inline: boolean) -> array[unknown]Render a turn's / result's files: inlined (content + handle note, so the model both sees the image and learns the handle to replay) while fresh, the handle note alone once older — the model re-reads an old image on demand with view_image. A file with no recorded MIME type cannot inline.
Parameters
Returns
Wire view (JSON Schema)
gemini_content#
agentagent gemini_content(msg: types.message, inline: boolean) -> unknownOne history element as a Gemini content. inline marks the freshest element — the only one whose files inline their content (older elements show handles only, keeping the request small; the model re-reads any old image with view_image).
Parameters
Returns
Inferred type
agent(inline: boolean, msg: call_turn | result_turn | turn) -> unknown
Wire view (JSON Schema)
gemini_drop_schema_key#
agentagent gemini_drop_schema_key(key: string) -> booleanParameters
Returns
Wire view (JSON Schema)
gemini_sanitize#
agentagent gemini_sanitize(node: unknown) -> unknown with pureParameters
Returns
Effects
Inferred type
agent(node: unknown) -> unknown
Wire view (JSON Schema)
gemini_function_declaration#
agentagent gemini_function_declaration(meta: reflection.agent_metadata) -> unknownParameters
Returns
Inferred type
agent(meta: agent_metadata) -> unknown
Wire view (JSON Schema)
gemini_request_body#
agentagent gemini_request_body(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> unknownParameters
Returns
Inferred type
agent(client: connection, history: array[call_turn | result_turn | turn], tool_metas: array[agent_metadata]) -> unknown
Wire view (JSON Schema)
gemini_parts#
agentagent gemini_parts(parsed: unknown) -> array[unknown]Parameters
Returns
Wire view (JSON Schema)
gemini_is_call#
agentagent gemini_is_call(part: unknown) -> booleanParameters
Returns
Wire view (JSON Schema)
gemini_tool_call#
agentagent gemini_tool_call(part: unknown) -> types.tool_callParameters
Returns
Inferred type
agent(part: unknown) -> tool_call
Wire view (JSON Schema)
gemini_is_thought#
agentagent gemini_is_thought(part: unknown) -> booleanWhether a response part is the model's internal thinking: Gemini interleaves thought summaries (marked thought: true) with the answer parts — never user-facing text.
Parameters
Returns
Wire view (JSON Schema)
gemini_parse_step#
agentagent gemini_parse_step(parsed: unknown) -> types.stepParameters
Returns
Inferred type
agent(parsed: unknown) -> (step_call | step_final)
Wire view (JSON Schema)
gemini_infer_step#
agentagent gemini_infer_step(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.stepOne Gemini model step over the history + tool schemas (the body built as a value tree and the reply parsed by post_json in Katari; only the POST leaves the runtime, and each inlined image file in the body base64s at that send boundary via the http.json slot contract).
Parameters
Returns
Inferred type
agent(client: connection, history: array[call_turn | result_turn | turn], tool_metas: array[agent_metadata]) -> (step_call | step_final) with throw[fetch_error | status_error | parse_error] | io
Wire view (JSON Schema)
ai.openai
13 declarationsconnection#
datadata connection(model: string, api_key: string of private, system: string)An OpenAI connection: the model id, the API key, and an optional system prompt. Provider handler state — built once per use openai.provider(...).
Parameters
Wire view (JSON Schema)
provider#
agentagent provider[R, effect E](model: string, api_key: string of private, system: string ?= "", continuation: agent(value: null) -> R with {...E, ai.infer_step}) -> R with E | io | prelude.throw[ai.step_error]Serve ai.infer_step over OpenAI for the extent of the continuation: use openai.provider(model = "gpt-4o-mini", api_key = env.get_secret(key = "OPENAI_API_KEY")). A step's provider failure (ai.step_error) is raised here, at the provider's use site.
Generics
Parameters
Returns
Effects
Inferred type
agent(api_key: string of private, continuation: agent(value: null) -> T0 with infer_step | E1, model: string, system?: string) -> T0 with throw[fetch_error | status_error | parse_error] | E1 | io
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
openai_role#
agentagent openai_role(role: string) -> stringParameters
Returns
Wire view (JSON Schema)
openai_call_entry#
agentagent openai_call_entry(call: types.tool_call, map: ai.name_map) -> unknownParameters
Returns
Inferred type
agent(call: tool_call, map: name_map) -> unknown
Wire view (JSON Schema)
openai_file_notes#
agentagent openai_file_notes(files: array[file]) -> stringA turn's files as text notes appended to its content. This provider keeps files handle-only (no image inlining — the Gemini and Anthropic providers are the multimodal ones); the handle note still lets the model replay a file into a tool call (view_image / send_files).
Parameters
Returns
Wire view (JSON Schema)
openai_messages_of#
agentagent openai_messages_of(msg: types.message, map: ai.name_map) -> array[unknown]Parameters
Returns
Inferred type
agent(map: name_map, msg: call_turn | result_turn | turn) -> array[unknown]
Wire view (JSON Schema)
openai_messages#
agentagent openai_messages(client: connection, history: array[types.message], map: ai.name_map) -> array[unknown]Parameters
Returns
Inferred type
agent(client: connection, history: array[call_turn | result_turn | turn], map: name_map) -> array[unknown]
Wire view (JSON Schema)
openai_function_tool#
agentagent openai_function_tool(meta: reflection.agent_metadata, map: ai.name_map) -> unknownParameters
Returns
Inferred type
agent(map: name_map, meta: agent_metadata) -> unknown
Wire view (JSON Schema)
openai_request_body#
agentagent openai_request_body(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata], map: ai.name_map) -> unknownParameters
Returns
Inferred type
agent(client: connection, history: array[call_turn | result_turn | turn], map: name_map, tool_metas: array[agent_metadata]) -> unknown
Wire view (JSON Schema)
openai_message#
agentagent openai_message(parsed: unknown) -> unknownParameters
Returns
Wire view (JSON Schema)
openai_tool_call_of#
agentagent openai_tool_call_of(tc: unknown, map: ai.name_map) -> types.tool_call with prelude.throw[json.parse_error]Parameters
Returns
Inferred type
agent(map: name_map, tc: unknown) -> tool_call with throw[parse_error]
Wire view (JSON Schema)
openai_parse_step#
agentagent openai_parse_step(response: unknown, map: ai.name_map) -> types.step with prelude.throw[json.parse_error]Parameters
Returns
Inferred type
agent(map: name_map, response: unknown) -> (step_call | step_final) with throw[parse_error]
Wire view (JSON Schema)
openai_infer_step#
agentagent openai_infer_step(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.stepOne OpenAI model step over the history + tool schemas (the body built as a value tree and the reply parsed by post_json, in Katari; only the POST leaves the runtime).
Parameters
Returns
Inferred type
agent(client: connection, history: array[call_turn | result_turn | turn], tool_metas: array[agent_metadata]) -> (step_call | step_final) with throw[fetch_error | status_error | parse_error] | io
Wire view (JSON Schema)
ai.types
9 declarationsturn#
datadata turn(role: string, text: string, files: array[file])One turn in a conversation: who spoke ("user" / "model"), what they said, and any files attached to it (a Discord attachment, a generated image). Files ride as blob handles; each provider decides when to inline their content for the model (see the providers' inline policy).
Parameters
Wire view (JSON Schema)
tool_call#
datadata tool_call(name: string, opaque: string, args: unknown)One tool the model wants to invoke this step — name is its function name (the dispatch key and the model-facing call/response record), opaque is any provider token that must be replayed verbatim on the call turn (Gemini's thought signature, OpenAI's call id), args is the model's argument object as a parsed value (replayed verbatim into the call turn, and dispatched straight through reflection.call_agent, whose delegation boundary unconditionally turns any $katari_ref the model replayed into the file it denotes).
Parameters
Wire view (JSON Schema)
tool_result#
datadata tool_result(name: string, call_id: string, text: string, files: array[file])A tool's output for the model, keyed by the tool name / call_id so it round-trips as a function response. files are the file values found in the result (a screenshot an MCP tool returned, an edited image): text already shows their $katari_ref handles (the result's wire form), and the provider additionally inlines their content for the model while the result is fresh.
Parameters
Wire view (JSON Schema)
call_turn#
datadata call_turn(calls: array[tool_call])A conversation turn recording the model's tool-call batch — replayed so the next step shows the model what it already asked for.
Parameters
Wire view (JSON Schema)
result_turn#
datadata result_turn(results: array[tool_result])A conversation turn carrying the results of a tool-call batch.
Parameters
Wire view (JSON Schema)
message#
typetype message = turn | call_turn | result_turnDefinition
step_final#
datadata step_final(text: string)One inference step's decision: a final reply text for the user.
Parameters
Wire view (JSON Schema)
step_call#
datadata step_call(calls: array[tool_call])One inference step's decision: invoke a batch of tools (dispatched in parallel, results fed back together).
Parameters
Wire view (JSON Schema)
step#
typetype step = step_final | step_callDefinition