ri is a Rust workspace that ports the core runtime pieces of
pi into Rust:
ri-llm-provideris the Rust counterpart ofpackages/ai/@earendil-works/pi-ai.ri-agent-coreis the Rust counterpart ofpackages/agent/@earendil-works/pi-agent-core.
The goal is behavioral compatibility with pi's LLM provider and agent runtime semantics, expressed with Rust's type system, traits, serde data models, and async streams instead of a direct TypeScript API translation.
| Rust surface | Pi package | Purpose |
|---|---|---|
ri-llm-provider |
packages/ai / pi-ai |
Unified multi-provider LLM API, model registry, streaming events, tool calls, usage tracking, provider payloads, OAuth helpers, and image APIs. |
ri-agent-core |
packages/agent / pi-agent-core |
Stateful agent runtime with event streaming, tool execution, message queues, lifecycle events, and turn orchestration. |
ri_agent_core::harness |
packages/agent harness utilities |
Session storage, prompt formatting, compaction, skills/resources, local execution env, provider hooks, and harness-level orchestration utilities. |
The workspace contains the Rust implementation and local test coverage for the core pi-ai and pi-agent-core behavior that is practical to verify without live provider credentials.
As of the latest local verification, cargo test --workspace passes 1469
Rust tests with 0 failures. Those numbers are tracked in detail in
MIGRATION_STATUS.md, and a case-by-case parity audit
against pi HEAD (1179 pi test cases, each classified as covered, gated-live,
or not-applicable with reasons) lives in
docs/TEST_PARITY_AUDIT.md.
The test suite covers provider metadata, payload generation, streaming parsers, SSE and eventstream behavior, abort handling, response IDs, usage accounting, message transforms, simple stream option defaults, tool calling, reasoning/thinking controls, agent loop control flow, stateful agents, custom stream providers, proxy streaming, tool execution with partial update events, provider request/payload/response hooks, compaction, resources, session storage, OAuth credential metadata, bash execution session messages, local execution environment behavior, and harness utilities.
Live provider E2E tests are not run by default. Provider behavior is covered locally through mock HTTP servers, payload assertions, parser tests, and stream event tests.
The migration target is pi's LLM provider and agent-core behavior, not every
test in the pi monorepo. The active source baseline is the full
packages/ai/test + packages/agent/test case list from pi HEAD — 1179
cases, enumerated one-by-one in
docs/TEST_PARITY_AUDIT.md with a per-case
verdict (covered, gated live-credential smoke, Node-specific, or out of
scope with reasons). The baseline intentionally excludes
packages/coding-agent.
Rust test totals must not be used as a one-to-one completion signal. The Rust suite currently includes both:
- Pi exact case parity: behavior that corresponds to a specific pi-ai or pi-agent-core source test case.
- Rust-specific coverage: behavior needed because ri owns Rust-native implementations for HTTP transport, proxy construction, OAuth auth storage, proxy stream forwarding, streaming parsers, session storage, and harness integration.
New tests should be added only when they cover a missing Pi exact case or a
clearly necessary Rust-specific contract. Tests that inspect Rust source,
TypeScript source, Markdown files, Cargo.toml, or test names to prove
coverage should not be added. Coverage claims should come from behavior tests,
gated live tests, and explicit notes in MIGRATION_STATUS.md.
The Rust-representable local migration has no known remaining Pi exact-case gap, but it is not externally certified by count alone. Strict provider parity still requires running the gated live/E2E matrix with real credentials, local model services where applicable, and manual OAuth flows.
ri-llm-provider includes:
- Built-in model lookup with
get_model(provider, model_id). stream,complete,stream_simple, andcomplete_simpleAPIs.- A
ri-aiCLI counterpart for listing providers and running supported OAuth login flows. - OpenAI, Azure OpenAI Responses, OpenAI Codex, Anthropic, Google, Vertex AI, Mistral, Amazon Bedrock, GitHub Copilot, OpenRouter, xAI, Radius, OpenAI-compatible, and related compatibility layers.
- An embedded model catalog plus an ri-owned refresh generator
(
cargo run --bin generate-models) that merges models.dev data under a conservative update policy (allowlisted providers, per-component cost guards, tier freezes). - Tool calling with streamed partial arguments.
- Text, image, thinking, and tool-result content blocks.
- Conservative parsing for common incomplete streamed tool-argument JSON, including recovery of completed object/array prefixes when the trailing field or value is still incomplete.
- Tool-call argument validation with pi-style JSON-schema coercion, including JS-number-like primitive coercions for plain serialized schemas and TypeBox-like object/array/combinator constraints.
- A Rust-native
StringEnumOptions/string_enum_schemahelper for the provider-friendly schema shape produced by pi-ai'sStringEnumutility. - Reasoning levels including
off,minimal,low,medium,high, andxhigh, with provider-specific wire mappings. - Pi-style simple stream defaults for output token limits and budget-based thinking token adjustment.
- Usage and cost accounting, response IDs, diagnostics, abort flags, retry configuration, session IDs, and provider-specific extra options.
- Provider payload and response hooks for Rust-native inspection/adaptation of request bodies and HTTP/faux response metadata.
- GitHub Copilot dynamic headers for user/agent initiation and image-capable requests across supported OpenAI/Anthropic-compatible paths.
- OAuth helpers for providers such as OpenAI Codex, Anthropic, GitHub Copilot,
Google Vertex, and xAI, including source display metadata, local callback pages
for browser-based flows, source-shaped
~/.pi/agent/auth.jsoncredential storage, and OpenAI CodexaccountIdcredential preservation.
ri-agent-core includes:
- Stateful
Agentand lower-levelagent_loopAPIs. - Event streaming for agent, turn, message, and tool execution events, including partial tool execution updates with Pi-style raw tool-call arguments in start/update events.
- Parallel and sequential tool execution, including per-call lifecycle and tool-result event ordering for sequential batches.
- Tool call and tool result hooks, including pre-execution blocking with error tool results, tool-result error-flag overrides, and assistant/context snapshots for low-level hook implementations.
- Steering and follow-up queues.
- Context transforms and custom
AgentMessageconversion before LLM calls. - Dynamic API key providers for resolving refreshed credentials before each low-level LLM request.
- Custom stream providers, including proxy streaming through
/api/stream. - Prompt templates, skills/resources loading, including Pi-style skill ignore-file glob and escaped-comment semantics, session storage, compaction, Pi-style session tree navigation, and local execution environment utilities.
- Harness prompt context conversion for LLM messages, bash execution messages, custom session messages, branch summaries, and compaction summaries.
- Pi-style skill metadata validation diagnostics and prompt-template argument substitution.
ri_agent_core::harness includes:
AgentHarness, a higher-level runtime facade aroundAgent.- Persistent and in-memory session storage, including a SQLite backend with write-side materialized session state and lazy cursor-based entry reads.
- System prompt formatting with model, thinking level, active tools, resources, session, and local environment context.
- Provider request, payload, and after-response events for harness subscribers and hooks.
- Pi-style harness error classifications for session, hook/auth, compaction, branch-summary, and unknown failures.
- Skills and prompt template loading.
- Branch summary and context compaction helpers, including Pi-style auth requirements for generated summaries.
- Local execution environment utilities for file and shell operations.
- Provider auth, request, and payload hooks.
- Before-agent-start, context, tool-call, and tool-result hooks.
- Model selection, thinking-level selection, resource updates, queue updates, save points, aborts, and settled lifecycle events.
The workspace crates are versioned in lockstep. The major.minor component
tracks the pi release line ri is behavior-compatible with (0.81 = pi
0.81.x); the patch component is ri-owned and advances for ri bug fixes and
small baseline syncs. Each release entry in CHANGELOG.md
records the exact pi baseline (version and commit), and releases are tagged
(v0.81.0) with matching
GitHub Releases. The crates are not
published to crates.io yet; consume them as Git dependencies.
Add the crates as path dependencies inside this repository or as Git dependencies pinned to a release tag:
[dependencies]
ri-llm-provider = { git = "https://github.com/nowa/ri.git", tag = "v0.81.0" }
ri-agent-core = { git = "https://github.com/nowa/ri.git", tag = "v0.81.0" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }Drop the tag field to track main instead.
use ri_llm_provider::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let model = get_model("openai", "gpt-5-mini").expect("model");
let context = Context {
system_prompt: Some("You are helpful.".to_owned()),
messages: vec![Message::User(UserMessage::text("Hello"))],
tools: vec![],
};
let mut options = StreamOptions::default();
options.api_key = std::env::var("OPENAI_API_KEY").ok();
let message = complete(&model, context, options).await?;
println!("{message:#?}");
Ok(())
}use ri_agent_core::*;
use ri_llm_provider::*;
#[tokio::main]
async fn main() -> Result<(), String> {
let model = get_model("openai", "gpt-5-mini").expect("model");
let agent = Agent::new(AgentOptions::new(model));
agent.subscribe(|event| {
println!("{event:?}");
});
agent.prompt("Hello from ri").await?;
Ok(())
}ri-llm-provider uses the same conceptual context as pi-ai:
system_prompt- ordered
messages - optional tool definitions
- assistant output made of typed content blocks
- tool results that can be fed back into later turns
The Rust representation uses enums and structs such as Context, Message,
AssistantContent, ToolCall, ToolResultMessage, and Usage.
ri-agent-core distinguishes between flexible agent-level messages and LLM
messages:
AgentMessage[] -> transform_context -> AgentMessage[] -> convert_to_llm -> Message[] -> LLM
This mirrors pi-agent-core's AgentMessage vs LLM message model. Custom
application messages can be kept in agent state and filtered or converted before
each provider request.
Agent runs emit events for:
AgentStart/AgentEndTurnStart/TurnEndMessageStart/MessageUpdate/MessageEndToolExecutionStart/ToolExecutionUpdate/ToolExecutionEnd
Tool execution start/update events expose the assistant's raw tool-call
arguments, while tool executors and hooks receive validated/prepared arguments.
The Agent updates its state from these events and notifies sync or async
subscribers.
The harness is a core part of ri-agent-core, but it is intentionally kept as
the ri_agent_core::harness module instead of a separate crate. It sits above
the lower-level Agent and owns the application-facing runtime concerns that a
coding agent or UI needs around the raw agent loop.
AgentHarness combines:
Sessionstate and storage.LocalExecutionEnvfile and shell utilities.- model and thinking-level selection.
- active tool filtering and tool execution mode.
- loaded skills and prompt templates.
- generated system prompts.
- provider authentication and stream option patching.
- provider payload patching before network dispatch.
- message queues for steering, follow-up, and next-turn work.
- session tree navigation events and editable target text restoration.
- save-point and settlement events for durable app state.
Session context built by the harness preserves pi-style custom messages, branch summaries, and compaction summaries as LLM user context. The local execution environment supports per-command working directories, shell environment overrides, stdout/stderr streaming callbacks, timeout/abort termination, and shell capture with truncated output logs.
This keeps crate boundaries simple while still making the harness API visible as a first-class Rust surface.
Run all tests:
cargo test --workspaceTests that mutate process environment variables serialize themselves behind a shared lock, so the default parallel test runner is safe.
Useful focused commands:
cargo test -p ri-llm-provider
cargo test -p ri-agent-core
cargo fmtWhen changing migration coverage, update MIGRATION_STATUS.md with the exact parity or Rust-specific reason instead of relying on the raw test count.
The workspace currently targets Rust 2024 edition.
- See CONTRIBUTING.md for development and pull request guidelines.
- See SECURITY.md for vulnerability reporting.
- See THIRD_PARTY_NOTICES.md for dependency license notes.
This is not a source-compatible port of the TypeScript API. The compatibility target is pi's behavior and protocol shape:
- provider payload semantics
- stream event ordering
- tool call and tool result behavior
- reasoning/thinking controls
- context handoff between providers
- abort and error behavior
- agent loop and state transitions
TypeScript-specific pieces such as TypeBox declarations and dynamic module
loading are represented with Rust-native equivalents where applicable, primarily
serde, serde_json::Value, traits, and explicit provider modules.
MIT