rhema-core 0.2.2

A small, embeddable, generic step engine for one exchange with a model
Documentation
//! The streaming seam: events emitted during a step and the sink that
//! receives them.
//!
//! Ownership of events: the [`crate::ModelProvider`] emits streaming deltas
//! (`ThinkingDelta`, `TextDelta`, `ToolCallStart`, `ToolCallArgsDelta`,
//! `Usage`) while it streams; the [`crate::Agent`] emits the lifecycle events
//! (`StepStart`, `ToolResult`, `StepEnd`). A provider that announces a tool
//! call while streaming it is not echoed by the engine — the engine only
//! emits `ToolCallStart` for calls the provider did not announce.

/// One streaming or lifecycle event of a step.
#[derive(Debug, Clone, PartialEq)]
pub enum Event<'a> {
    /// The step begins; `iteration` counts the steps this agent has already
    /// completed.
    StepStart {
        /// Zero-based index of this step within the agent.
        iteration: u32,
    },
    /// A piece of the model's reasoning trace, in stream order. Ephemeral
    /// by design: never appended to the session history — sinks render it
    /// live (or drop it), and it is gone when the step ends.
    ThinkingDelta(&'a str),
    /// A piece of assistant text, in stream order.
    TextDelta(&'a str),
    /// A tool call begins streaming; args follow as deltas.
    ToolCallStart {
        /// The call's correlation id.
        id: &'a str,
        /// The tool's name.
        name: &'a str,
    },
    /// A piece of a tool call's JSON arguments, in stream order. The id
    /// names the call the piece belongs to: the wire formats let a server
    /// interleave the fragments of parallel calls, so sinks must not
    /// assume the call that started last is the one still streaming.
    ToolCallArgsDelta {
        /// The streaming call's correlation id.
        id: &'a str,
        /// A piece of the call's JSON arguments.
        delta: &'a str,
    },
    /// A chunk of a running tool's human-facing output — a command's
    /// completed lines — in stream order. Ephemeral like
    /// [`Event::ThinkingDelta`]: never appended to the session history;
    /// sinks render it live, and the full text arrives in the call's
    /// `ToolResult`.
    ToolOutputDelta {
        /// The running call's correlation id.
        id: &'a str,
        /// A chunk of the tool's output; complete lines, newline
        /// included.
        chunk: &'a str,
    },
    /// A streaming tool reported its own live status — what its inner
    /// work is doing and what it has spent. Ephemeral like
    /// [`Event::ToolOutputDelta`]: sinks render it live, never into the
    /// session history, and a later status for the same call replaces it.
    ToolStatus {
        /// The running call's correlation id.
        id: &'a str,
        /// The tool's status.
        status: &'a crate::tool::ToolStatus,
    },
    /// A tool result was appended to the session history.
    ToolResult {
        /// The answered call's id.
        id: &'a str,
        /// Result payload.
        content: &'a str,
        /// Whether the call failed.
        is_error: bool,
        /// The process exit code of command-like tools; `None` otherwise.
        code: Option<i32>,
    },
    /// The running token tally for the in-flight model call, as the
    /// provider reports it inside the stream itself. The fields are
    /// cumulative for the call — each event replaces the previous tally,
    /// never adds to it. Emitted whenever the wire carries a usage frame:
    /// Anthropic-shaped wires report the prompt with `message_start` and
    /// the completion with cumulative `message_delta` frames; Gemini
    /// reports on every chunk; OpenAI-shaped wires only on the final
    /// chunk, so there the event adds nothing over [`Event::StepEnd`].
    /// Sinks that only need the final accounting may ignore it.
    Usage(crate::output::Usage),
    /// A provider attempt failed transiently and the retry ladder is
    /// waiting `delay` before the next one. Ephemeral like
    /// [`Event::ThinkingDelta`]: sinks render it live — a countdown, a
    /// notice — and it never reaches the session history.
    RetryWait {
        /// The failed attempt's own error text.
        error: &'a str,
        /// How long the ladder waits before the next attempt.
        delay: std::time::Duration,
        /// One-based number of the attempt that just failed.
        attempt: u32,
        /// Total attempts the policy will spend before giving up.
        total: u32,
    },
    /// The step ends.
    StepEnd {
        /// Token accounting for the model call.
        usage: crate::output::Usage,
        /// Why the model stopped.
        stop_reason: &'a crate::output::StopReason,
    },
}

/// Receives a step's events. Drivers implement this to stream into a UI, a
/// log, or a channel; a simple collector is a `Vec` behind a closure.
///
/// `Send` because the sink reference crosses await points inside provider
/// implementations, which must produce `Send` futures.
pub trait EventSink: Send {
    /// Handle one event. Called synchronously, in stream order.
    fn emit(&mut self, event: Event<'_>);
}