Browse modules

ai

v0.1.0 · compiled with katari 0.1.0

Overview

from the package README

ai — 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 exposes ai.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's generateContent API. use gemini.provider(model = ..., api_key = ...) serves ai.infer_step for the continuation. Images: yes — a turn's image files inline as inlineData parts (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 for gemini.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 = ...)model defaults to claude-sonnet-5 and max_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 as image content 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_KEYuse gemini.provider(api_key = env.get_secret(key = "GEMINI_API_KEY"))
    • OpenAI: OPENAI_API_KEYuse openai.provider(api_key = env.get_secret(key = "OPENAI_API_KEY"))
    • Anthropic: ANTHROPIC_API_KEYuse anthropic.provider(api_key = env.get_secret(key = "ANTHROPIC_API_KEY"))

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 declarations

infer_step#

request
request infer_step(history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.step

One 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

history

Returns

Wire view (JSON Schema)

step_error#

type
type step_error = json.parse_error | http.status_error | http.fetch_error

loop_error#

type
type loop_error = reflection.call_error

reply#

agent
agent reply(history: array[types.message]) -> string

Infer a single reply with no tools (the common chat case).

Parameters

history

Returns

string

Inferred type

agent(history: array[call_turn | result_turn | turn]) -> string with infer_step
Wire view (JSON Schema)

view_image#

agent
agent view_image(image: file) -> file

Tool: 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

image
file

Returns

file
Wire view (JSON Schema)

infer_with_tools#

agent
agent infer_with_tools[effect E](history: array[types.message], tools: array[agent never -> unknown with E], max_steps: integer) -> string

Run 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

Eeffect

Parameters

history
tools
array
agent
input
never
output
unknown
effects
E
max_steps
integer

Returns

string

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#

agent
agent dispatch_one[effect E](tools: array[agent never -> unknown with E], call: types.tool_call) -> types.tool_result

Find 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

Eeffect

Parameters

tools
array
agent
input
never
output
unknown
effects
E

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#

agent
agent collect_result_files(node: unknown) -> array[file] with pure

Collect 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

node
unknown

Returns

array
file

Effects

pure

Inferred type

agent(node: unknown) -> array[file]
Wire view (JSON Schema)

ai_turn#

effect
effect ai_turn

The 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#

request
request observation(source: string, content: string, files: array[file]) -> string

THE 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

source

Which monitor is reporting (e.g. "slack").

string
content

The report to inject into the model's context.

string
files

Any files attached to the report (a Discord / Slack attachment), injected on the report's turn so the provider inlines them for the model.

array
file

Returns

string
Wire view (JSON Schema)

region_session#

data
data 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

conversation
registry
Wire view (JSON Schema)

region_turn_result#

data
data 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

reply
string
Wire view (JSON Schema)

region_step_result#

data
data 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

registry
Wire view (JSON Schema)

region_batch#

data
data 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

registry
results
Wire view (JSON Schema)

cancel_monitor#

agent
agent cancel_monitor(monitor_id: string) -> string

Tool: 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

monitor_id

The id you were given when the monitor started.

string

Returns

string
Wire view (JSON Schema)

route_region_call#

agent
agent 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_result

Route 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 monitorsregion.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

Eeffect

Parameters

nursery

The nursery to fork / cancel monitors in.

one of
override
base
E
overrides
io
tools

The synchronous tools (called and awaited).

array
agent
input
never
output
unknown
effects
E
monitors

The startable monitors (forked detached).

array
agent
input
object
input
null
output
never
effects
one of
override
base
E
overrides
io
registry

The started-monitor registry (id → fiber).

call

The model's tool call.

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 | io

Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.

region_turn_loop#

agent
agent 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_result

One 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

Eeffect

Parameters

nursery

The nursery to fork / cancel monitors in.

one of
override
base
E
overrides
io
conversation

The conversation to advance (the observation already injected).

tools

The synchronous tools.

array
agent
input
never
output
unknown
effects
E
monitors

The startable monitors.

array
agent
input
object
input
null
output
never
effects
one of
override
base
E
overrides
io
registry

The started-monitor registry.

max_steps

The per-turn step budget.

integer

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 | io

Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.

infer_with_region#

agent
agent 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.") -> never

Run 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

Eeffect

Parameters

context

The conversation the model always sees (system framing, prior turns).

tools

Synchronous tools: called and awaited, the result fed back.

array
agent
input
never
output
unknown
effects
E
monitors

Startable monitors: forked detached when the model calls one, they notify through observation.

array
agent
input
object
input
null
output
never
effects
one of
override
base
E
overrides
io
sources

Always-on input fibers forked at startup (not shown as tools).

array
agent
input
object
input
null
output
never
effects
one of
override
base
E
overrides
io
max_steps

The per-observation step budget.

integer
region_instructions?= "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."

How the model should use the monitor start / stop tools; override to specialise.

string

Returns

never

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#

data
data 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

real_to_wire

Qualified tool name → wire name.

record
string
wire_to_real

Wire name → qualified tool name.

record
string
Wire view (JSON Schema)

sanitize_name#

agent
agent sanitize_name(name: string) -> string

Fold 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

name

The qualified tool name.

string

Returns

string
Wire view (JSON Schema)

suffixed_name#

agent
agent suffixed_name(base: string, index: integer) -> string

Disambiguate a colliding wire name with an ordinal suffix (_2, _3, …), reserving room so the result still fits the 64-character cap.

Parameters

base

The colliding base wire name.

string
index

The 1-based ordinal.

integer

Returns

string
Wire view (JSON Schema)

assign_wire_name#

agent
agent assign_wire_name(taken: record[string], real: string) -> string

Choose 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

taken

The wire names already assigned this step.

record
string
real

The qualified name to place.

string

Returns

string

Inferred type

agent(real: string, taken: record[string]) -> string
Wire view (JSON Schema)

build_name_map#

agent
agent build_name_map(tool_metas: array[reflection.agent_metadata]) -> name_map

Build 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

tool_metas

The step's tools, in order.

Returns

Inferred type

agent(tool_metas: array[agent_metadata]) -> name_map
Wire view (JSON Schema)

wire_name_of#

agent
agent wire_name_of(map: name_map, real: string) -> string

The 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

map

The step's name map.

real

The qualified tool name.

string

Returns

string
Wire view (JSON Schema)

real_name_of#

agent
agent real_name_of(map: name_map, wire: string) -> string

The 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

map

The step's name map.

wire

The wire name the model returned.

string

Returns

string
Wire view (JSON Schema)

ai.anthropic

19 declarations

connection#

data
data 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

model

The Claude model id.

string
api_key

The API key, sent as the x-api-key header.

of private
string
system

The system prompt; empty sends none.

string
max_tokens

The required max_tokens: the output-token cap of one step.

integer
Wire view (JSON Schema)

provider#

agent
agent 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

Rtype
Eeffect

Parameters

model?= "claude-sonnet-5"

The Claude model id.

string
api_key

The API key, sent as the x-api-key header.

of private
string
system?= ""

An optional system prompt, sent as the top-level system field.

string
max_tokens?= 4096

The output-token cap of one step; the Messages API requires one on every request.

integer
continuation

The computation to run with ai.infer_step served.

agent
input
object
value
null
output
R
effects
override
base
E
overrides

Returns

R

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#

agent
agent anthropic_role(role: string) -> string

Map the vocabulary role onto the Messages API role: our "model" speaks as "assistant".

Parameters

role

The ai.types turn role ("user" / "model").

string

Returns

string
Wire view (JSON Schema)

anthropic_file_notes#

agent
agent anthropic_file_notes(files: array[file]) -> string

Every 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

files

The files attached to a turn.

array
file

Returns

string
Wire view (JSON Schema)

anthropic_content_type#

agent
agent anthropic_content_type(attachment: file) -> string

The 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

attachment

The file to read.

file

Returns

string
Wire view (JSON Schema)

anthropic_is_image#

agent
agent anthropic_is_image(attachment: file) -> boolean

Whether 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

attachment

The file to test.

file

Returns

boolean
Wire view (JSON Schema)

anthropic_text_block#

agent
agent anthropic_text_block(text: string) -> unknown

A text content block — { "type": "text", "text": @text@ }, built as a raw value tree.

Parameters

text

The block's text.

string

Returns

unknown
Wire view (JSON Schema)

anthropic_image_block#

agent
agent anthropic_image_block(attachment: file) -> unknown

An 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

attachment

The image file to inline.

file

Returns

unknown
Wire view (JSON Schema)

anthropic_turn_content#

agent
agent anthropic_turn_content(text: string, files: array[file]) -> unknown

A 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

text

The turn's text.

string
files

The turn's files.

array
file

Returns

unknown

Inferred type

agent(files: array[file], text: string) -> unknown
Wire view (JSON Schema)

anthropic_call_block#

agent
agent anthropic_call_block(call: types.tool_call, map: ai.name_map) -> unknown

Parameters

call

One tool call of the model's batch.

map

The step's wire-name map.

Returns

unknown

Inferred type

agent(call: tool_call, map: name_map) -> unknown
Wire view (JSON Schema)

anthropic_result_block#

agent
agent anthropic_result_block(result: types.tool_result) -> unknown

Parameters

result

One tool's output, keyed back to its call.

Returns

unknown

Inferred type

agent(result: tool_result) -> unknown
Wire view (JSON Schema)

anthropic_message#

agent
agent anthropic_message(message: types.message, map: ai.name_map) -> unknown

Parameters

message

One history element.

map

The step's wire-name map.

Returns

unknown

Inferred type

agent(map: name_map, message: call_turn | result_turn | turn) -> unknown
Wire view (JSON Schema)

anthropic_tool#

agent
agent anthropic_tool(meta: reflection.agent_metadata, map: ai.name_map) -> unknown

Parameters

meta

One tool's derived schema.

map

The step's wire-name map.

Returns

unknown

Inferred type

agent(map: name_map, meta: agent_metadata) -> unknown
Wire view (JSON Schema)

anthropic_request_body#

agent
agent anthropic_request_body(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata], map: ai.name_map) -> unknown

Parameters

client

The provider connection.

history

The conversation so far.

tool_metas

The tools' derived schemas.

map

The step's wire-name map.

Returns

unknown

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#

agent
agent anthropic_blocks(parsed: unknown) -> array[unknown]

Parameters

parsed

The parsed response document.

unknown

Returns

array
unknown
Wire view (JSON Schema)

anthropic_block_kind#

agent
agent anthropic_block_kind(block: unknown) -> string

Parameters

block

One response content block.

unknown

Returns

string
Wire view (JSON Schema)

anthropic_tool_call#

agent
agent anthropic_tool_call(block: unknown, map: ai.name_map) -> types.tool_call

Parameters

block

A tool_use response block.

unknown
map

The step's wire-name map.

Inferred type

agent(block: unknown, map: name_map) -> tool_call
Wire view (JSON Schema)

anthropic_parse_step#

agent
agent anthropic_parse_step(parsed: unknown, map: ai.name_map) -> types.step

Parameters

parsed

The parsed response document.

unknown
map

The step's wire-name map.

Returns

Inferred type

agent(map: name_map, parsed: unknown) -> (step_call | step_final)
Wire view (JSON Schema)

anthropic_infer_step#

agent
agent anthropic_infer_step(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.step

One 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

client

The provider connection.

history

The conversation so far.

tool_metas

The tools' derived schemas.

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 declarations

connection#

data
data 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

model
string
api_key
of private
string
system
string
Wire view (JSON Schema)

provider#

agent
agent 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

Rtype
Eeffect

Parameters

model
string
api_key
of private
string
system?= ""
string
continuation
agent
input
object
value
null
output
R
effects
override
base
E
overrides

Returns

R

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#

agent
agent gemini_role(role: string) -> string

Parameters

role
string

Returns

string
Wire view (JSON Schema)

gemini_text_part#

agent
agent gemini_text_part(text: string) -> unknown

Parameters

text
string

Returns

unknown
Wire view (JSON Schema)

gemini_call_part#

agent
agent gemini_call_part(call: types.tool_call) -> unknown

Parameters

Returns

unknown

Inferred type

agent(call: tool_call) -> unknown
Wire view (JSON Schema)

gemini_result_part#

agent
agent gemini_result_part(result: types.tool_result) -> unknown

Parameters

Returns

unknown

Inferred type

agent(result: tool_result) -> unknown
Wire view (JSON Schema)

gemini_content_type#

agent
agent gemini_content_type(f: file) -> string

The 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

f
file

Returns

string
Wire view (JSON Schema)

gemini_inline_part#

agent
agent gemini_inline_part(f: file) -> unknown

A 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

f
file

Returns

unknown
Wire view (JSON Schema)

gemini_handle_part#

agent
agent gemini_handle_part(f: file) -> unknown

A 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

f
file

Returns

unknown
Wire view (JSON Schema)

gemini_file_parts#

agent
agent 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

files
array
file
inline
boolean

Returns

array
unknown
Wire view (JSON Schema)

gemini_content#

agent
agent gemini_content(msg: types.message, inline: boolean) -> unknown

One 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

inline
boolean

Returns

unknown

Inferred type

agent(inline: boolean, msg: call_turn | result_turn | turn) -> unknown
Wire view (JSON Schema)

gemini_drop_schema_key#

agent
agent gemini_drop_schema_key(key: string) -> boolean

Parameters

key
string

Returns

boolean
Wire view (JSON Schema)

gemini_sanitize#

agent
agent gemini_sanitize(node: unknown) -> unknown with pure

Parameters

node
unknown

Returns

unknown

Effects

pure

Inferred type

agent(node: unknown) -> unknown
Wire view (JSON Schema)

gemini_function_declaration#

agent
agent gemini_function_declaration(meta: reflection.agent_metadata) -> unknown

Returns

unknown

Inferred type

agent(meta: agent_metadata) -> unknown
Wire view (JSON Schema)

gemini_request_body#

agent
agent gemini_request_body(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> unknown

Parameters

history

Returns

unknown

Inferred type

agent(client: connection, history: array[call_turn | result_turn | turn], tool_metas: array[agent_metadata]) -> unknown
Wire view (JSON Schema)

gemini_parts#

agent
agent gemini_parts(parsed: unknown) -> array[unknown]

Parameters

parsed
unknown

Returns

array
unknown
Wire view (JSON Schema)

gemini_is_call#

agent
agent gemini_is_call(part: unknown) -> boolean

Parameters

part
unknown

Returns

boolean
Wire view (JSON Schema)

gemini_tool_call#

agent
agent gemini_tool_call(part: unknown) -> types.tool_call

Parameters

part
unknown

Inferred type

agent(part: unknown) -> tool_call
Wire view (JSON Schema)

gemini_is_thought#

agent
agent gemini_is_thought(part: unknown) -> boolean

Whether 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

part
unknown

Returns

boolean
Wire view (JSON Schema)

gemini_parse_step#

agent
agent gemini_parse_step(parsed: unknown) -> types.step

Parameters

parsed
unknown

Returns

Inferred type

agent(parsed: unknown) -> (step_call | step_final)
Wire view (JSON Schema)

gemini_infer_step#

agent
agent gemini_infer_step(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.step

One 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

history

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 declarations

connection#

data
data 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

model
string
api_key
of private
string
system
string
Wire view (JSON Schema)

provider#

agent
agent 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

Rtype
Eeffect

Parameters

model
string
api_key
of private
string
system?= ""
string
continuation
agent
input
object
value
null
output
R
effects
override
base
E
overrides

Returns

R

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#

agent
agent openai_role(role: string) -> string

Parameters

role
string

Returns

string
Wire view (JSON Schema)

openai_call_entry#

agent
agent openai_call_entry(call: types.tool_call, map: ai.name_map) -> unknown

Returns

unknown

Inferred type

agent(call: tool_call, map: name_map) -> unknown
Wire view (JSON Schema)

openai_file_notes#

agent
agent openai_file_notes(files: array[file]) -> string

A 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

files
array
file

Returns

string
Wire view (JSON Schema)

openai_messages_of#

agent
agent openai_messages_of(msg: types.message, map: ai.name_map) -> array[unknown]

Parameters

Returns

array
unknown

Inferred type

agent(map: name_map, msg: call_turn | result_turn | turn) -> array[unknown]
Wire view (JSON Schema)

openai_messages#

agent
agent openai_messages(client: connection, history: array[types.message], map: ai.name_map) -> array[unknown]

Parameters

Returns

array
unknown

Inferred type

agent(client: connection, history: array[call_turn | result_turn | turn], map: name_map) -> array[unknown]
Wire view (JSON Schema)

openai_function_tool#

agent
agent openai_function_tool(meta: reflection.agent_metadata, map: ai.name_map) -> unknown

Returns

unknown

Inferred type

agent(map: name_map, meta: agent_metadata) -> unknown
Wire view (JSON Schema)

openai_request_body#

agent
agent openai_request_body(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata], map: ai.name_map) -> unknown

Parameters

Returns

unknown

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#

agent
agent openai_message(parsed: unknown) -> unknown

Parameters

parsed
unknown

Returns

unknown
Wire view (JSON Schema)

openai_tool_call_of#

agent
agent openai_tool_call_of(tc: unknown, map: ai.name_map) -> types.tool_call with prelude.throw[json.parse_error]

Parameters

tc
unknown

Inferred type

agent(map: name_map, tc: unknown) -> tool_call with throw[parse_error]
Wire view (JSON Schema)

openai_parse_step#

agent
agent openai_parse_step(response: unknown, map: ai.name_map) -> types.step with prelude.throw[json.parse_error]

Parameters

response
unknown

Returns

Inferred type

agent(map: name_map, response: unknown) -> (step_call | step_final) with throw[parse_error]
Wire view (JSON Schema)

openai_infer_step#

agent
agent openai_infer_step(client: connection, history: array[types.message], tool_metas: array[reflection.agent_metadata]) -> types.step

One 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

history

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 declarations

turn#

data
data 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

role
string
text
string
files
array
file
Wire view (JSON Schema)

tool_call#

data
data 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

name
string
opaque
string
args
unknown
Wire view (JSON Schema)

tool_result#

data
data 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

name
string
call_id
string
text
string
files
array
file
Wire view (JSON Schema)

call_turn#

data
data 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

calls
Wire view (JSON Schema)

result_turn#

data
data result_turn(results: array[tool_result])

A conversation turn carrying the results of a tool-call batch.

Parameters

results
Wire view (JSON Schema)

message#

type
type message = turn | call_turn | result_turn

Definition

step_final#

data
data step_final(text: string)

One inference step's decision: a final reply text for the user.

Parameters

text
string
Wire view (JSON Schema)

step_call#

data
data step_call(calls: array[tool_call])

One inference step's decision: invoke a batch of tools (dispatched in parallel, results fed back together).

Parameters

calls
Wire view (JSON Schema)

step#

type
type step = step_final | step_call

Definition