moonllm

Type-safe, OpenAI-compatible LLM client for MoonBit: chat completions, streaming (SSE), tool calling, and multimodal input.

llm
openai
ai
sdk
http-client
moon add DC-Z-lab/moonllm@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
last month
Downloads
378

Dependencies

README

#DC-Z-lab/moonllm

#
LLMError

pub(all) suberror LLMError {
Transport(String)
ApiError(code~ : Int, message~ : String)
Decode(String)
Stream(String)
} derive(
Debug
)

Errors raised by the moonllm client.

#
LLMError::to_string

fn LLMError::to_string(self : LLMError) -> String

#
AuthScheme

pub(all) enum AuthScheme {
Bearer
ApiKeyHeader
NoAuth
} derive(Eq,
Debug
)

How the API key is presented to the server.

#
Backoff

pub(all) enum Backoff {
Constant(delay_ms~ : Int)
Exponential(base_ms~ : Int, max_ms~ : Int)
ExponentialJitter(base_ms~ : Int, max_ms~ : Int)
} derive(Eq,
Debug
)

The backoff strategy used between retries.

#
Batch

pub(all) struct Batch {
id : String
status : String
input_file_id : String
output_file_id : String?
error_file_id : String?
counts : BatchCounts
} derive(
Debug
)

A batch job object.

#
Batch::is_terminal

fn Batch::is_terminal(self : Batch) -> Bool

Whether the batch has reached a terminal state.

#
BatchCounts

pub(all) struct BatchCounts {
total : Int
completed : Int
failed : Int
} derive(
Debug
)

Per-request counts for a batch job.

#
BatchRequest

pub(all) struct BatchRequest {
input_file_id : String
endpoint : String
completion_window : String
}

A request to create a batch job (POST /batches).

#
BatchRequest::new

fn BatchRequest::new(input_file_id : String, endpoint? : String, completion_window? : String) -> BatchRequest

Create a batch request. endpoint is the API path the batch runs against (e.g. /v1/chat/completions); completion_window is typically "24h".

#
ChatRequest

pub(all) struct ChatRequest {
model : String
messages : Array[Message]
temperature : Double?
max_tokens : Int?
top_p : Double?
stop : Array[String]?
tools : Array[Tool]?
tool_choice : ToolChoice?
frequency_penalty : Double?
presence_penalty : Double?
seed : Int?
n : Int?
response_format : ResponseFormat?
logit_bias : Map[String, Double]?
user : String?
stream : Bool
}

A chat completion request. Build with ChatRequest::new and the chainable setters, or construct the struct directly.

#
ChatRequest::frequency_penalty

fn ChatRequest::frequency_penalty(self : ChatRequest, p : Double) -> ChatRequest

#
ChatRequest::json_mode

fn ChatRequest::json_mode(self : ChatRequest) -> ChatRequest

Shortcut: request a JSON-object response ("JSON mode").

#
ChatRequest::logit_bias

fn ChatRequest::logit_bias(self : ChatRequest, bias : Map[String, Double]) -> ChatRequest

#
ChatRequest::max_tokens

fn ChatRequest::max_tokens(self : ChatRequest, n : Int) -> ChatRequest

#
ChatRequest::n

fn ChatRequest::n(self : ChatRequest, count : Int) -> ChatRequest

#
ChatRequest::new

fn ChatRequest::new(model : String, messages : Array[Message]) -> ChatRequest

Create a request for model with the given messages.

#
ChatRequest::presence_penalty

fn ChatRequest::presence_penalty(self : ChatRequest, p : Double) -> ChatRequest

#
ChatRequest::response_format

fn ChatRequest::response_format(self : ChatRequest, f : ResponseFormat) -> ChatRequest

#
ChatRequest::seed

fn ChatRequest::seed(self : ChatRequest, s : Int) -> ChatRequest

#
ChatRequest::stop

fn ChatRequest::stop(self : ChatRequest, s : Array[String]) -> ChatRequest

#
ChatRequest::temperature

fn ChatRequest::temperature(self : ChatRequest, t : Double) -> ChatRequest

#
ChatRequest::tool_choice

fn ChatRequest::tool_choice(self : ChatRequest, c : ToolChoice) -> ChatRequest

#
ChatRequest::tools

fn ChatRequest::tools(self : ChatRequest, t : Array[Tool]) -> ChatRequest

#
ChatRequest::top_p

fn ChatRequest::top_p(self : ChatRequest, p : Double) -> ChatRequest

#
ChatRequest::user

fn ChatRequest::user(self : ChatRequest, u : String) -> ChatRequest

#
ChatRequest::validate

fn ChatRequest::validate(self : ChatRequest) -> String?

Validate the request parameters, returning an error message if any value is out of its documented range. Returns None when the request is valid.

This is a best-effort local check against the OpenAI-documented ranges; it does not guarantee the server will accept the request.

#
ChatResponse

pub(all) struct ChatResponse {
id : String
object : String
created : Int64
model : String
choices : Array[Choice]
usage : Usage?
system_fingerprint : String?
} derive(
Debug
)

A non-streaming chat completion response.

#
ChatResponse::finish_reason

fn ChatResponse::finish_reason(self : ChatResponse) -> String?

The finish reason of the first choice, if any.

#
ChatResponse::first

fn ChatResponse::first(self : ChatResponse) -> Choice?

The first choice, if the response contains any.

#
ChatResponse::has_tool_calls

fn ChatResponse::has_tool_calls(self : ChatResponse) -> Bool

Whether the first choice requested one or more tool calls.

#
ChatResponse::text

fn ChatResponse::text(self : ChatResponse) -> String

Convenience: the text content of the first choice, if any.

#
ChatResponse::tool_calls

fn ChatResponse::tool_calls(self : ChatResponse) -> Array[ToolCall]

The tool calls requested by the first choice, if any.

#
ChatResponse::total_tokens

fn ChatResponse::total_tokens(self : ChatResponse) -> Int

The total tokens used, or 0 if usage was not reported.

#
Choice

pub(all) struct Choice {
index : Int
message : Message
finish_reason : String?
} derive(Eq,
Debug
)

A single choice in a chat completion response.

#
Choice::truncated

fn Choice::truncated(self : Choice) -> Bool

Whether this choice was cut off because it hit the token limit.

#
Choice::wants_tools

fn Choice::wants_tools(self : Choice) -> Bool

Whether this choice ended by requesting tool calls.

#
Client

pub struct Client {
api_key : String
base_url : String
timeout_ms : Int
extra_headers : Map[String, String]
auth : AuthScheme
}

An LLM client bound to an OpenAI-compatible endpoint.

Construct with Client::new, optionally overriding base_url. The default base_url targets the OpenAI public API; point it at any OpenAI-compatible gateway (Azure, local vLLM, router services, ...).

#
Client::batch

async fn Client::batch(self : Client, id : String) -> Batch raise LLMError

Retrieve a batch job's status (GET /batches/{id}).

#
Client::cancel_batch

async fn Client::cancel_batch(self : Client, id : String) -> Batch raise LLMError

Cancel a batch job (POST /batches/{id}/cancel).

#
Client::cancel_fine_tune

async fn Client::cancel_fine_tune(self : Client, id : String) -> FineTuneJob raise LLMError

Cancel a fine-tuning job (POST /fine_tuning/jobs/{id}/cancel).

#
Client::chat

async fn Client::chat(self : Client, request : ChatRequest) -> ChatResponse raise LLMError

Perform a non-streaming chat completion.

Raises LLMError on transport failure, non-2xx status, or decode failure.

#
Client::chat_anthropic

async fn Client::chat_anthropic(self : Client, request : ChatRequest) -> ChatResponse raise LLMError

Perform a chat completion against an Anthropic Messages endpoint.

This assumes the client's base_url targets an Anthropic-compatible host (default https://api.anthropic.com/v1). Note that Anthropic requires the x-api-key and anthropic-version headers; configure these via a client whose auth is set accordingly, or use this against a proxy that injects them. The request/response translation is handled here.

#
Client::chat_stream

async fn Client::chat_stream(self : Client, request : ChatRequest, on_delta : (String) -> Unit) -> String raise LLMError

Perform a streaming chat completion (text only).

on_delta is invoked for each incremental text fragment as it arrives. Returns the fully accumulated assistant text once the stream completes.

Raises LLMError on transport failure, non-2xx status, or decode failure.

#
Client::chat_stream_full

async fn Client::chat_stream_full(self : Client, request : ChatRequest, on_chunk : (StreamChunk) -> Unit) -> StreamResult raise LLMError

Perform a streaming chat completion, invoking on_chunk for every parsed StreamChunk as it arrives, and returning the fully assembled result (text + tool calls) once the stream completes.

This is the low-level streaming entry point. For the common text-only case, prefer chat_stream.

Raises LLMError on transport failure, non-2xx status, or decode failure.

#
Client::chat_with_policy

async fn Client::chat_with_policy(self : Client, request : ChatRequest, policy : RetryPolicy) -> ChatResponse raise LLMError

Perform a chat completion with retries governed by an explicit policy.

On a retryable ApiError, if the policy honors Retry-After and the error message contains a parseable hint, that delay is preferred over the computed backoff.

#
Client::chat_with_retry

async fn Client::chat_with_retry(self : Client, request : ChatRequest, max_retries? : Int, base_delay_ms? : Int) -> ChatResponse raise LLMError

Perform a chat completion with automatic retries on transient failures.

Retries up to max_retries times with exponential backoff starting at base_delay_ms. Non-retryable errors (4xx other than 429, decode errors) are raised immediately.

#
Client::completion

async fn Client::completion(self : Client, request : CompletionRequest) -> CompletionResponse raise LLMError

Perform a legacy text completion.

#
Client::create_batch

async fn Client::create_batch(self : Client, request : BatchRequest) -> Batch raise LLMError

Create a batch job (POST /batches).

#
Client::create_fine_tune

async fn Client::create_fine_tune(self : Client, request : FineTuneRequest) -> FineTuneJob raise LLMError

Create a fine-tuning job.

#
Client::delete_file

async fn Client::delete_file(self : Client, id : String) -> DeletionResult raise LLMError

Delete a file (DELETE /files/{id}).

#
Client::delete_json

async fn Client::delete_json(self : Client, path : String) -> Json raise LLMError

Perform a DELETE request to path and return the parsed response JSON.

#
Client::embeddings

async fn Client::embeddings(self : Client, request : EmbeddingRequest) -> EmbeddingResponse raise LLMError

Perform an embeddings request.

#
Client::file

async fn Client::file(self : Client, id : String) -> FileObject raise LLMError

Retrieve metadata for a single file (GET /files/{id}).

#
Client::files

async fn Client::files(self : Client) -> FileList raise LLMError

List the files stored at the endpoint (GET /files).

#
Client::fine_tune

async fn Client::fine_tune(self : Client, id : String) -> FineTuneJob raise LLMError

Retrieve a fine-tuning job (GET /fine_tuning/jobs/{id}).

#
Client::fine_tunes

async fn Client::fine_tunes(self : Client) -> FineTuneJobList raise LLMError

List fine-tuning jobs (GET /fine_tuning/jobs).

#
Client::generate_image

async fn Client::generate_image(self : Client, request : ImageRequest) -> ImageResponse raise LLMError

Generate one or more images from a text prompt.

#
Client::get_json

async fn Client::get_json(self : Client, path : String) -> Json raise LLMError

Perform a GET request to path and return the parsed response JSON.

#
Client::model

async fn Client::model(self : Client, id : String) -> Model raise LLMError

Retrieve metadata for a single model (GET /models/{id}).

#
Client::models

async fn Client::models(self : Client) -> ModelList raise LLMError

List the models available at the endpoint (GET /models).

#
Client::moderations

async fn Client::moderations(self : Client, request : ModerationRequest) -> ModerationResponse raise LLMError

Perform a moderation request.

#
Client::new

fn Client::new(api_key : String, base_url? : String, timeout_ms? : Int) -> Client

Create a client with the given API key.

  • base_url defaults to https://api.openai.com/v1.
  • timeout_ms defaults to 60_000.

#
Client::post_json

async fn Client::post_json(self : Client, path : String, body : Json) -> Json raise LLMError

Perform a POST with a JSON body to path and return the parsed response JSON. Shared by all non-streaming endpoints. Handles transport failures, non-2xx status, and JSON parse errors uniformly.

#
Client::respond

async fn Client::respond(self : Client, request : ResponseRequest) -> ResponseResult raise LLMError

Perform a request against the Responses API (POST /responses).

#
Client::speech

async fn Client::speech(self : Client, request : SpeechRequest) -> Bytes raise LLMError

Synthesize speech from text. Returns the raw audio bytes.

#
ClientBuilder

pub struct ClientBuilder {
api_key : String
base_url : String
timeout_ms : Int
headers : Map[String, String]
auth : AuthScheme
}

A builder for Client, for when you need more than the key and base URL: custom headers, an organization id, a non-bearer auth scheme, or a specific timeout.

#
ClientBuilder::anthropic

fn ClientBuilder::anthropic(self : ClientBuilder, version? : String) -> ClientBuilder

Configure the builder for the Anthropic Messages API: x-api-key auth and the required anthropic-version header.

#
ClientBuilder::api_key

fn ClientBuilder::api_key(self : ClientBuilder, key : String) -> ClientBuilder

#
ClientBuilder::auth

fn ClientBuilder::auth(self : ClientBuilder, scheme : AuthScheme) -> ClientBuilder

Set the authentication scheme.

#
ClientBuilder::base_url

fn ClientBuilder::base_url(self : ClientBuilder, url : String) -> ClientBuilder

#
ClientBuilder::build

fn ClientBuilder::build(self : ClientBuilder) -> Client

Finalize the builder into a Client.

#
ClientBuilder::header

fn ClientBuilder::header(self : ClientBuilder, name : String, value : String) -> ClientBuilder

Add a header sent with every request.

#
ClientBuilder::new

Start building a client.

#
ClientBuilder::organization

fn ClientBuilder::organization(self : ClientBuilder, org : String) -> ClientBuilder

Set the OpenAI-Organization header.

#
ClientBuilder::timeout_ms

fn ClientBuilder::timeout_ms(self : ClientBuilder, ms : Int) -> ClientBuilder

#
CompletionChoice

pub(all) struct CompletionChoice {
index : Int
text : String
finish_reason : String?
} derive(
Debug
)

#
CompletionRequest

pub(all) struct CompletionRequest {
model : String
prompt : String
max_tokens : Int?
temperature : Double?
top_p : Double?
stop : Array[String]?
suffix : String?
}

A request to the legacy /completions (text completion) endpoint.

This is the pre-chat completion API, still supported by some models and many OpenAI-compatible gateways for raw prompt completion.

#
CompletionRequest::max_tokens

fn CompletionRequest::max_tokens(self : CompletionRequest, n : Int) -> CompletionRequest

#
CompletionRequest::new

fn CompletionRequest::new(model : String, prompt : String) -> CompletionRequest

Create a completion request.

#
CompletionRequest::stop

#
CompletionRequest::temperature

fn CompletionRequest::temperature(self : CompletionRequest, t : Double) -> CompletionRequest

#
CompletionResponse

pub(all) struct CompletionResponse {
id : String
model : String
choices : Array[CompletionChoice]
usage : Usage?
} derive(
Debug
)

A response from the legacy /completions endpoint.

#
CompletionResponse::text

fn CompletionResponse::text(self : CompletionResponse) -> String

The text of the first completion choice, if any.

#
Content

pub(all) enum Content {
Str(String)
Parts(Array[ContentPart])
} derive(Eq,
Debug
)

The content of a message: either a single text string or a list of parts.
impl Show for Content
impl ToJson for Content

#
Content::to_text

fn Content::to_text(self : Content) -> String

The plain-text view of the content: the string itself, or the text parts of a multimodal message concatenated together.

#
ContentPart

pub(all) enum ContentPart {
Text(String)
ImageUrl(String)
} derive(Eq,
Debug
)

A single part of a multimodal message content.

OpenAI-compatible: a message's content may be a plain string or an array of content parts. Text and ImageUrl cover the common cases.

#
ContentPart::image_base64

fn ContentPart::image_base64(mime : String, data : String) -> ContentPart

An image content part from raw base64 data, wrapped as a data: URI.

mime is e.g. "image/png" or "image/jpeg".

#
ContentPart::image_url

fn ContentPart::image_url(url : String) -> ContentPart

An image content part from a URL (https://... or a data: URI).

#
ContentPart::text

fn ContentPart::text(s : String) -> ContentPart

A text content part.

#
Conversation

pub struct Conversation {
system : String?
messages : Array[Message]
}

A mutable conversation: an ordered list of messages plus an optional system prompt, with helpers for appending turns, estimating token usage, and trimming history to fit a budget.

This is a convenience layer over the raw Array[Message]; it does not itself call the API — turn it into a request with to_request.

#
Conversation::add_response

fn Conversation::add_response(self : Conversation, response : ChatResponse) -> String

Append the assistant message from a ChatResponse (the first choice), so the reply becomes part of the ongoing history. Returns the appended text.

#
Conversation::assistant

fn Conversation::assistant(self : Conversation, text : String) -> Unit

Append an assistant message.

#
Conversation::clear

fn Conversation::clear(self : Conversation) -> Unit

Remove all messages (keeping the system prompt).

#
Conversation::estimate_tokens

fn Conversation::estimate_tokens(self : Conversation) -> Int

Estimate the total token count of the conversation (system + all messages).

#
Conversation::length

fn Conversation::length(self : Conversation) -> Int

The number of messages currently in the history (excluding the system prompt).

#
Conversation::new

fn Conversation::new(system? : String) -> Conversation

Create an empty conversation, optionally with a system prompt.

#
Conversation::push

fn Conversation::push(self : Conversation, message : Message) -> Unit

Append an arbitrary message.

#
Conversation::set_system

fn Conversation::set_system(self : Conversation, prompt : String) -> Unit

Set (or replace) the system prompt.

#
Conversation::to_messages

fn Conversation::to_messages(self : Conversation) -> Array[Message]

Snapshot the full message list including the system prompt (if any) as the leading system message.

#
Conversation::to_request

fn Conversation::to_request(self : Conversation, model : String) -> ChatRequest

Build a ChatRequest for model from the conversation's current state.

#
Conversation::tool_result

fn Conversation::tool_result(self : Conversation, tool_call_id : String, content : String) -> Unit

Append a tool-result message.

#
Conversation::trim_to_budget

fn Conversation::trim_to_budget(self : Conversation, max_tokens : Int) -> Int

Trim the oldest messages until the estimated token count fits within max_tokens, preserving the system prompt and the most recent messages.

Returns the number of messages dropped.

#
Conversation::user

fn Conversation::user(self : Conversation, text : String) -> Unit

Append a user message.

#
DeletionResult

pub(all) struct DeletionResult {
id : String
deleted : Bool
} derive(
Debug
)

#
Embedding

pub(all) struct Embedding {
index : Int
embedding : Array[Double]
} derive(
Debug
)

A single embedding vector in an embeddings response.

#
EmbeddingInput

pub(all) enum EmbeddingInput {
Single(String)
Batch(Array[String])
} derive(Eq,
Debug
)

The input to an embeddings request: one or more strings to embed.

#
EmbeddingRequest

pub(all) struct EmbeddingRequest {
model : String
input : EmbeddingInput
dimensions : Int?
user : String?
encoding_format : String?
}

A request to the /embeddings endpoint.

#
EmbeddingRequest::dimensions

fn EmbeddingRequest::dimensions(self : EmbeddingRequest, n : Int) -> EmbeddingRequest

Set the target dimensionality of the returned embeddings.

#
EmbeddingRequest::new

fn EmbeddingRequest::new(model : String, input : EmbeddingInput) -> EmbeddingRequest

Create an embeddings request for a single text.

#
EmbeddingRequest::of_batch

fn EmbeddingRequest::of_batch(model : String, texts : Array[String]) -> EmbeddingRequest

Convenience: build a request embedding a batch of strings.

#
EmbeddingRequest::of_text

fn EmbeddingRequest::of_text(model : String, text : String) -> EmbeddingRequest

Convenience: build a request embedding a single string.

#
EmbeddingRequest::user

fn EmbeddingRequest::user(self : EmbeddingRequest, u : String) -> EmbeddingRequest

Set the end-user identifier.

#
EmbeddingResponse

pub(all) struct EmbeddingResponse {
model : String
data : Array[Embedding]
usage : Usage?
} derive(
Debug
)

The response from the /embeddings endpoint.

#
EmbeddingResponse::vector

fn EmbeddingResponse::vector(self : EmbeddingResponse) -> Array[Double]

The first embedding vector, if any.

#
FileList

pub(all) struct FileList {
data : Array[FileObject]
} derive(
Debug
)

#
FileObject

pub(all) struct FileObject {
id : String
bytes : Int64
created_at : Int64
filename : String
purpose : String
} derive(
Debug
)

Metadata for a file stored at the endpoint (/files).

#
FineTuneJob

pub(all) struct FineTuneJob {
id : String
model : String
status : String
training_file : String
fine_tuned_model : String?
} derive(
Debug
)

A fine-tuning job object.

#
FineTuneJob::is_done

fn FineTuneJob::is_done(self : FineTuneJob) -> Bool

Whether the job has finished (successfully or not).

#
FineTuneJobList

pub(all) struct FineTuneJobList {
data : Array[FineTuneJob]
} derive(
Debug
)

#
FineTuneRequest

pub(all) struct FineTuneRequest {
model : String
training_file : String
validation_file : String?
suffix : String?
n_epochs : Int?
}

A request to create a fine-tuning job (POST /fine_tuning/jobs).

#
FineTuneRequest::n_epochs

fn FineTuneRequest::n_epochs(self : FineTuneRequest, n : Int) -> FineTuneRequest

Set the number of training epochs.

#
FineTuneRequest::new

fn FineTuneRequest::new(model : String, training_file : String) -> FineTuneRequest

Create a fine-tuning request from a base model and a training file id.

#
FineTuneRequest::suffix

fn FineTuneRequest::suffix(self : FineTuneRequest, s : String) -> FineTuneRequest

Set a suffix for the resulting model name.

#
FineTuneRequest::validation_file

fn FineTuneRequest::validation_file(self : FineTuneRequest, id : String) -> FineTuneRequest

Set the validation file id.

#
FunctionCall

pub(all) struct FunctionCall {
name : String
arguments : String
} derive(Eq, ToJson,
Debug
,
FromJson
)

A function invocation requested by the model.

#
GeneratedImage

pub(all) struct GeneratedImage {
url : String?
b64_json : String?
revised_prompt : String?
} derive(
Debug
)

A single generated image: either a URL or base64-encoded data.

#
ImageRequest

pub(all) struct ImageRequest {
prompt : String
model : String?
n : Int?
size : String?
quality : String?
response_format : String?
}

A request to the /images/generations endpoint.

#
ImageRequest::model

fn ImageRequest::model(self : ImageRequest, m : String) -> ImageRequest

#
ImageRequest::n

fn ImageRequest::n(self : ImageRequest, count : Int) -> ImageRequest

#
ImageRequest::new

fn ImageRequest::new(prompt : String) -> ImageRequest

Create an image generation request from a prompt.

#
ImageRequest::quality

fn ImageRequest::quality(self : ImageRequest, q : String) -> ImageRequest

#
ImageRequest::size

fn ImageRequest::size(self : ImageRequest, s : String) -> ImageRequest

Set the output size, e.g. "1024x1024".

#
ImageResponse

pub(all) struct ImageResponse {
created : Int64
data : Array[GeneratedImage]
} derive(
Debug
)

A response from the /images/generations endpoint.

#
ImageResponse::urls

fn ImageResponse::urls(self : ImageResponse) -> Array[String]

All non-empty image URLs in the response.

#
LogLevel

pub(all) enum LogLevel {
Debug
Info
Warn
Error
} derive(Eq,
Debug
)

A log severity level.

#
LogLevel::label

fn LogLevel::label(self : LogLevel) -> String

A short label for a level.

#
LogLevel::rank

fn LogLevel::rank(self : LogLevel) -> Int

The numeric rank of a level, for threshold comparisons.

#
MemoryLogger

pub struct MemoryLogger {
min_level : LogLevel
lines : Array[String]
}

A simple collecting logger that buffers formatted lines in memory — useful for tests and for surfacing a request trace in a UI. A production app would swap in its own observer that writes to a real logging backend.

#
MemoryLogger::clear

fn MemoryLogger::clear(self : MemoryLogger) -> Unit

Clear all buffered lines.

#
MemoryLogger::count

fn MemoryLogger::count(self : MemoryLogger) -> Int

The number of buffered log lines.

#
MemoryLogger::dump

fn MemoryLogger::dump(self : MemoryLogger) -> String

All buffered lines joined by newlines.

#
MemoryLogger::has_at_least

fn MemoryLogger::has_at_least(self : MemoryLogger, level : LogLevel) -> Bool

Whether any line at level or above was recorded.

#
MemoryLogger::log

fn MemoryLogger::log(self : MemoryLogger, level : LogLevel, message : String) -> Unit

Log a message at a level (dropped if below the threshold).

#
MemoryLogger::log_request

fn MemoryLogger::log_request(self : MemoryLogger, log : RequestLog) -> Unit

Log a completed request round-trip at an appropriate level.

#
MemoryLogger::new

fn MemoryLogger::new(min_level? : LogLevel) -> MemoryLogger

Create a memory logger that keeps entries at or above min_level.

#
Message

pub(all) struct Message {
role : Role
content : Content
tool_calls : Array[ToolCall]?
tool_call_id : String?
name : String?
} derive(Eq,
Debug
)

A chat message.
impl ToJson for Message

#
Message::assistant

fn Message::assistant(text : String) -> Message

#
Message::has_image

fn Message::has_image(self : Message) -> Bool

Whether the message contains any image part.

#
Message::image_count

fn Message::image_count(self : Message) -> Int

Count the image parts in a message.

#
Message::system

fn Message::system(text : String) -> Message

Convenience constructors for messages.

#
Message::tool_result

fn Message::tool_result(tool_call_id : String, content : String) -> Message

Build a tool role message carrying the result of a tool call.

#
Message::user

fn Message::user(text : String) -> Message

#
Message::user_parts

fn Message::user_parts(parts : Array[ContentPart]) -> Message

Build a multimodal user message from content parts (text and/or images).

#
MessageBuilder

pub struct MessageBuilder {
role : Role
parts : Array[ContentPart]
name : String?
}

Fluent builder for a multimodal message, so callers can assemble mixed text/image content without manually constructing the Content enum.

Example:
let msg = MessageBuilder::user() .text("What is in these images?") .image_url("https://a/1.png") .image_url("https://a/2.png") .build()

#
MessageBuilder::assistant

fn MessageBuilder::assistant() -> MessageBuilder

Start building an assistant message.

#
MessageBuilder::build

Finalize into a Message.

If the builder holds exactly one text part and no name, it collapses to a plain string content (the common case); otherwise it emits a parts array.

#
MessageBuilder::image_base64

fn MessageBuilder::image_base64(self : MessageBuilder, mime : String, data : String) -> MessageBuilder

Append a base64 image part.

#
MessageBuilder::image_url

fn MessageBuilder::image_url(self : MessageBuilder, url : String) -> MessageBuilder

Append an image-URL part.

#
MessageBuilder::name

fn MessageBuilder::name(self : MessageBuilder, n : String) -> MessageBuilder

Set the optional author name.

#
MessageBuilder::new

Start building a message with the given role.

#
MessageBuilder::part_count

fn MessageBuilder::part_count(self : MessageBuilder) -> Int

The number of content parts accumulated so far.

#
MessageBuilder::system

Start building a system message.

#
MessageBuilder::text

fn MessageBuilder::text(self : MessageBuilder, s : String) -> MessageBuilder

Append a text part.

#
MessageBuilder::user

Start building a user message.

#
Model

pub(all) struct Model {
id : String
object : String
created : Int64
owned_by : String
} derive(
Debug
)

Metadata describing a single model available at the endpoint.

#
ModelList

pub(all) struct ModelList {
data : Array[Model]
} derive(
Debug
)

The response from the GET /models endpoint.

#
ModelList::ids

fn ModelList::ids(self : ModelList) -> Array[String]

The ids of all listed models.

#
ModerationRequest

pub(all) struct ModerationRequest {
input : EmbeddingInput
model : String?
}

A request to the /moderations endpoint.

#
ModerationRequest::model

fn ModerationRequest::model(self : ModerationRequest, m : String) -> ModerationRequest

Set the moderation model explicitly.

#
ModerationRequest::of_batch

fn ModerationRequest::of_batch(texts : Array[String]) -> ModerationRequest

Create a moderation request for a batch of texts.

#
ModerationRequest::of_text

fn ModerationRequest::of_text(text : String) -> ModerationRequest

Create a moderation request for a single text.

#
ModerationResponse

pub(all) struct ModerationResponse {
model : String
results : Array[ModerationResult]
} derive(
Debug
)

The response from the /moderations endpoint.

#
ModerationResponse::any_flagged

fn ModerationResponse::any_flagged(self : ModerationResponse) -> Bool

Whether any input in the batch was flagged.

#
ModerationResult

pub(all) struct ModerationResult {
flagged : Bool
categories : Map[String, Bool]
category_scores : Map[String, Double]
} derive(
Debug
)

A single moderation result: whether the input was flagged, and which categories were triggered.

#
ModerationResult::flagged_categories

fn ModerationResult::flagged_categories(self : ModerationResult) -> Array[String]

The categories that were flagged, in no particular order.

#
RequestLog

pub(all) struct RequestLog {
path : String
request_body : String
status : Int
response_body : String
ok : Bool
}

A record describing a single request/response round-trip, passed to an observer for logging, metrics, or debugging.

#
ResponseFormat

pub(all) enum ResponseFormat {
TextFormat
JsonObject
JsonSchema(name~ : String, schema~ : Json)
} derive(Eq,
Debug
)

The requested response format.

#
ResponseRequest

pub(all) struct ResponseRequest {
model : String
input : String
instructions : String?
max_output_tokens : Int?
temperature : Double?
}

A minimal wrapper for OpenAI's Responses API (/responses), a newer unified endpoint that accepts a single input (string or message list) and returns an output array of items.

This covers the common text-in/text-out case; the full Responses API has a much larger surface (tools, state, streaming) that a real client would extend over time.

#
ResponseRequest::instructions

fn ResponseRequest::instructions(self : ResponseRequest, text : String) -> ResponseRequest

Set top-level instructions (akin to a system prompt).

#
ResponseRequest::max_output_tokens

fn ResponseRequest::max_output_tokens(self : ResponseRequest, n : Int) -> ResponseRequest

#
ResponseRequest::new

fn ResponseRequest::new(model : String, input : String) -> ResponseRequest

Create a Responses request with a plain-text input.

#
ResponseRequest::temperature

fn ResponseRequest::temperature(self : ResponseRequest, t : Double) -> ResponseRequest

#
ResponseResult

pub(all) struct ResponseResult {
id : String
model : String
status : String
output_text : String
usage : Usage?
} derive(
Debug
)

#
RetryPolicy

pub(all) struct RetryPolicy {
max_retries : Int
backoff : Backoff
respect_retry_after : Bool
}

A retry policy: how many times to retry and how long to wait between tries.

#
RetryPolicy::default

fn RetryPolicy::default() -> RetryPolicy

A sensible default policy: 3 retries with exponential backoff from 500ms, capped at 8s, honoring Retry-After.

#
RetryPolicy::delay_for

fn RetryPolicy::delay_for(self : RetryPolicy, attempt : Int) -> Int

Compute the delay in milliseconds before the given zero-based attempt (0 = the wait before the first retry).

#
RetryPolicy::new

fn RetryPolicy::new(max_retries : Int, backoff : Backoff, respect_retry_after? : Bool) -> RetryPolicy

Construct a custom retry policy.

#
Role

pub(all) enum Role {
System
User
Assistant
Tool
} derive(Eq,
Debug
)

The role of a chat message author.

#
Role::parse

fn Role::parse(s : String) -> Role

#
Role::to_string

fn Role::to_string(self : Role) -> String

#
SSEEvent

pub(all) struct SSEEvent {
event : String?
data : String
id : String?
retry : Int?
} derive(Eq,
Debug
)

A fully-framed Server-Sent Event.

Per the SSE spec, an event is terminated by a blank line and may carry multiple data: lines (concatenated with newlines), an event: type, an id:, and a retry: value.

#
SSEParser

pub struct SSEParser {
event : String?
data : StringBuilder
id : String?
retry : Int?
has_data : Bool
}

A stateful Server-Sent Events framer.

Feed it lines (without worrying about trailing CR/LF) via push_line; it returns a complete SSEEvent whenever a blank line terminates the current event. This correctly handles multi-line data: payloads, which the naive one-line-per-event approach cannot.

#
SSEParser::new

fn SSEParser::new() -> SSEParser

Create a fresh SSE parser.

#
SSEParser::push_line

fn SSEParser::push_line(self : SSEParser, raw : String) -> SSEEvent?

Feed one line into the parser (the trailing newline may be included or not).

Returns Some(event) when this line (a blank line) terminates an event, otherwise None.

#
Schema

pub(all) enum Schema {
StrSchema(description~ : String?)
NumSchema(description~ : String?)
IntSchema(description~ : String?)
BoolSchema(description~ : String?)
EnumSchema(values~ : Array[String], description~ : String?)
ArraySchema(items~ : Schema, description~ : String?)
ObjectSchema(fields~ : Array[(String, Schema)], required~ : Array[String], description~ : String?)
}

A small, type-safe builder for JSON Schema fragments.

LLM tool/function parameters are described with JSON Schema. Writing that schema by hand as a raw JSON string is error-prone; Schema lets you build it with typed constructors and render it to a Json value via to_json.

Example:
let params = Schema::object( fields=[ ("city", Schema::string(description="City name")), ("units", Schema::enum_(["celsius", "fahrenheit"])), ], required=["city"], )
impl ToJson for Schema

#
Schema::array

fn Schema::array(items : Schema, description? : String) -> Schema

An array schema over items.

#
Schema::boolean

fn Schema::boolean(description? : String) -> Schema

A boolean schema.

#
Schema::enum_

fn Schema::enum_(values : Array[String], description? : String) -> Schema

A string-enum schema.

#
Schema::integer

fn Schema::integer(description? : String) -> Schema

An integer schema.

#
Schema::number

fn Schema::number(description? : String) -> Schema

A number (float) schema.

#
Schema::object

fn Schema::object(fields~ : Array[(String, Schema)], required? : Array[String], description? : String) -> Schema

An object schema. fields maps property names to sub-schemas; required lists the names that must be present.

#
Schema::string

fn Schema::string(description? : String) -> Schema

A string schema.

#
SpeechRequest

pub(all) struct SpeechRequest {
model : String
input : String
voice : String
response_format : String?
speed : Double?
}

A request to the /audio/speech (text-to-speech) endpoint.

#
SpeechRequest::new

fn SpeechRequest::new(model : String, input : String, voice : String) -> SpeechRequest

Create a text-to-speech request.

#
SpeechRequest::response_format

fn SpeechRequest::response_format(self : SpeechRequest, fmt : String) -> SpeechRequest

Set the audio output format, e.g. "mp3", "wav", "opus".

#
SpeechRequest::speed

fn SpeechRequest::speed(self : SpeechRequest, s : Double) -> SpeechRequest

Set the playback speed (0.25–4.0).

#
StreamChunk

pub(all) struct StreamChunk {
content : String?
tool_calls : Array[ToolCallDelta]
finish_reason : String?
} derive(Eq,
Debug
)

A parsed delta from a streaming chat completion chunk.

#
StreamResult

pub(all) struct StreamResult {
content : String
tool_calls : Array[ToolCall]
finish_reason : String?
}

The complete result of a streaming chat completion.

#
TokenizerHint

pub(all) enum TokenizerHint {
GptLike
ClaudeLike
CharsPerToken(Double)
} derive(Eq,
Debug
)

A family of estimation heuristics with different characters-per-token ratios, since different model families tokenize at different densities.

#
Tool

pub(all) struct Tool {
name : String
description : String
parameters : Json
}

A tool (function) definition advertised to the model.
impl ToJson for Tool

#
Tool::with_schema

fn Tool::with_schema(name : String, description : String, parameters : Schema) -> Tool

Build a Tool from a name, description, and a typed Schema for its parameters — a type-safe alternative to writing the JSON Schema by hand.

#
ToolCall

pub(all) struct ToolCall {
id : String
function : FunctionCall
} derive(Eq,
Debug
)

A tool call requested by the assistant.
impl ToJson for ToolCall

#
ToolCallAccumulator

pub struct ToolCallAccumulator {
by_index : Map[Int, ToolCallBuilder]
order : Array[Int]
}

Accumulates streaming ToolCallDelta fragments into complete ToolCalls.

Feed each chunk's tool_calls via add; call finish to obtain the assembled list once the stream completes.

#
ToolCallAccumulator::add

fn ToolCallAccumulator::add(self : ToolCallAccumulator, deltas : Array[ToolCallDelta]) -> Unit

Fold a chunk's tool-call fragments into the accumulator.

#
ToolCallAccumulator::finish

Assemble the accumulated fragments into complete tool calls, in the order they first appeared.

#
ToolCallAccumulator::new

Create an empty accumulator.

#
ToolCallBuilder

type ToolCallBuilder

Internal mutable builder for one in-progress tool call.

#
ToolCallDelta

pub(all) struct ToolCallDelta {
index : Int
id : String?
name : String?
arguments : String?
} derive(Eq,
Debug
)

An incremental tool-call fragment within a streaming chunk.

During streaming, a tool call is delivered piecewise: the index identifies which call it belongs to, and any of id / name / arguments may be a partial fragment to be concatenated.

#
ToolChoice

pub(all) enum ToolChoice {
Auto
NoneChoice
Required
Function(String)
} derive(Eq,
Debug
)

How the model should choose among the provided tools.

#
TranscriptionResponse

pub(all) struct TranscriptionResponse {
text : String
language : String?
duration : Double?
} derive(
Debug
)

A response from the /audio/transcriptions endpoint.

#
Usage

pub(all) struct Usage {
prompt_tokens : Int
completion_tokens : Int
total_tokens : Int
} derive(Eq, ToJson,
Debug
,
FromJson
)

Token usage statistics returned by the API.

#
Usage::add

fn Usage::add(self : Usage, other : Usage) -> Usage

Add two usage records together (useful when aggregating across calls).

#
Usage::zero

fn Usage::zero() -> Usage

A zero-valued usage record.

#
UsageTracker

pub struct UsageTracker {
calls : Int
prompt_tokens : Int
completion_tokens : Int
prompt_price_per_1k : Double
completion_price_per_1k : Double
}

Aggregates token usage and call counts across many requests, so an application can report cumulative consumption (and estimated cost).

#
UsageTracker::calls

fn UsageTracker::calls(self : UsageTracker) -> Int

The number of calls recorded.

#
UsageTracker::cost

fn UsageTracker::cost(self : UsageTracker) -> Double

The estimated cost so far, in dollars, using the configured prices.

#
UsageTracker::new

fn UsageTracker::new(prompt_price_per_1k? : Double, completion_price_per_1k? : Double) -> UsageTracker

Create a tracker. Prices default to 0 (cost reported as 0 until set).

#
UsageTracker::record

fn UsageTracker::record(self : UsageTracker, usage : Usage) -> Unit

Record a usage entry (e.g. from a ChatResponse.usage).

#
UsageTracker::record_response

fn UsageTracker::record_response(self : UsageTracker, response : ChatResponse) -> Unit

Record the usage from a chat response, if it reported any.

#
UsageTracker::reset

fn UsageTracker::reset(self : UsageTracker) -> Unit

Reset all counters to zero (prices are preserved).

#
UsageTracker::snapshot

fn UsageTracker::snapshot(self : UsageTracker) -> Usage

A Usage snapshot of the accumulated totals.

#
UsageTracker::summary

fn UsageTracker::summary(self : UsageTracker) -> String

A one-line human-readable summary of the accumulated usage.

#
UsageTracker::total_tokens

fn UsageTracker::total_tokens(self : UsageTracker) -> Int

The total tokens across all recorded calls.

#
anthropic_request_body

fn anthropic_request_body(request : ChatRequest) -> Json

Build an Anthropic Messages request body from a common ChatRequest.

System messages are hoisted into the top-level system field; all other messages are mapped to Anthropic's {role, content} shape. Anthropic requires max_tokens, so a default of 1024 is used when unset.

#
build_query

fn build_query(params : Array[(String, String)]) -> String

Build a query string (without the leading ?) from key/value pairs, percent-encoding both keys and values. Returns an empty string when there are no parameters.

#
cosine_similarity

fn cosine_similarity(a : Array[Double], b : Array[Double]) -> Double

Compute the cosine similarity between two equal-length vectors.

Returns 0.0 if either vector is empty or their lengths differ.

#
estimate_cost

fn estimate_cost(prompt_tokens : Int, completion_tokens : Int, prompt_price_per_1k~ : Double, completion_price_per_1k~ : Double) -> Double

Approximate a US-dollar cost given token counts and per-1K-token prices.

#
estimate_message_tokens

fn estimate_message_tokens(message : Message) -> Int

Estimate the token count of a single message, including a small overhead per message for role/formatting (mirrors OpenAI's ~4 tokens/message).

#
estimate_messages_tokens

fn estimate_messages_tokens(messages : Array[Message], hint? : TokenizerHint) -> Int

Estimate the total prompt tokens for a list of messages under a hint, including a per-message overhead (role + formatting).

#
estimate_tokens

fn estimate_tokens(text : String) -> Int

A rough token-count estimate for a piece of text.

Uses the widely-cited heuristic of ~4 characters per token. This is an approximation for budgeting only, not an exact tokenizer.

#
estimate_tokens_by_words

fn estimate_tokens_by_words(text : String) -> Int

A word-boundary based estimate: counts whitespace-separated words plus a surcharge for punctuation-heavy text. Sometimes closer than the pure char-ratio for natural language.

#
estimate_tokens_with

fn estimate_tokens_with(text : String, hint : TokenizerHint) -> Int

Estimate the token count of text under a given hint.

#
gemini_request_body

fn gemini_request_body(request : ChatRequest) -> Json

Build a Gemini generateContent request body from a ChatRequest.

#
is_retryable_error

fn is_retryable_error(err : LLMError) -> Bool

Whether an error is retryable under this policy.

#
is_valid_speech_format

fn is_valid_speech_format(fmt : String) -> Bool

Whether fmt is a recognized speech output format.

#
join_url

fn join_url(base : String, path : String) -> String

Join a base URL and a path, collapsing a duplicated / at the boundary and inserting one if neither side provides it.

#
merge_consecutive

fn merge_consecutive(messages : Array[Message]) -> Array[Message]

Merge consecutive messages from the same role into a single message, joining their text with a blank line. Some APIs reject two user messages in a row; this collapses them.

#
parse_anthropic_event

fn parse_anthropic_event(event_type : String, payload : String) -> StreamChunk? raise LLMError

Parse one Anthropic streaming event, given its event: type and data: JSON payload, into a common StreamChunk.

Returns None for events that carry no usable delta (message_start, content_block_start/stop, ping, etc.). Raises LLMError::Decode on malformed JSON.

#
parse_anthropic_response

fn parse_anthropic_response(json : Json) -> ChatResponse raise LLMError

Parse an Anthropic Messages response into the common ChatResponse.

The content blocks of type text are concatenated into a single assistant message; usage is mapped from input_tokens/output_tokens.

#
parse_anthropic_tool_calls

fn parse_anthropic_tool_calls(json : Json) -> Array[ToolCall]

Parse the tool_use content blocks from a complete (non-streaming) Anthropic response into common ToolCalls.

Anthropic represents tool calls as {"type":"tool_use","id":...,"name":...,"input":{...}} blocks in the response content array; the input object is re-serialized into the arguments JSON string to match the OpenAI shape.

#
parse_gemini_response

fn parse_gemini_response(json : Json) -> ChatResponse raise LLMError

Parse a Gemini generateContent response into the common ChatResponse.

#
parse_retry_after

fn parse_retry_after(value : String) -> Int?

Parse a Retry-After header value into milliseconds.

Supports the delta-seconds form (e.g. "5" → 5000ms). The HTTP-date form is not supported and yields None.

#
parse_sse_line

fn parse_sse_line(line : String) -> (String, String)?

Strip a single leading SSE field prefix (data: / event: ...) and the optional single space after the colon, returning the field value.

Returns None for comment lines (starting with :) and blank lines.

#
parse_stream_data

fn parse_stream_data(payload : String) -> StreamChunk? raise LLMError

Parse one SSE data: payload (the JSON after data: ) into a StreamChunk.

Returns None for payloads that carry no usable delta (e.g. role-only opening chunks). Raises LLMError::Decode on malformed JSON.

#
parse_transcription

fn parse_transcription(json_text : String) -> TranscriptionResponse raise LLMError

Parse a transcription response from raw JSON text (as returned by the API).

The transcription endpoint itself is multipart/form-data for the audio upload, which is out of scope for the JSON client; this helper covers the response side so callers using a multipart layer can decode the result.

#
percent_encode

fn percent_encode(s : String) -> String

Percent-encode a string for use in a URL query value. Unreserved characters (A-Z a-z 0-9 - _ . ~) pass through; everything else is encoded as %XX using UTF-8 bytes.

#
render_transcript

fn render_transcript(messages : Array[Message]) -> String

Render a list of messages into a single plain-text transcript, one line per message prefixed by the role — handy for logging or debugging.

#
speech_formats

let speech_formats : Array[String]

The supported audio output formats for speech synthesis.

#
truncate_to_tokens

fn truncate_to_tokens(text : String, max_tokens : Int, hint? : TokenizerHint) -> String

Truncate text to at most max_tokens (estimated), returning the kept prefix. Useful for clamping a single oversized message.

#
with_query

fn with_query(path : String, params : Array[(String, String)]) -> String

Append a query string to a path, choosing ? or & appropriately.