hyperhive/hive-claude/src/lib.rs

77 lines
3.2 KiB
Rust

//! `hive-claude` — a small, reusable async driver for headless
//! `claude --print` (Claude Code CLI) sessions.
//!
//! It spawns the CLI, streams and classifies its `stream-json` output, and
//! reports the result as a `Result<(), Error>`: a clean turn is `Ok(())`, and
//! every non-completion state — both recognized sentinels (rate-limit,
//! prompt-too-long, …) and hard failures (spawn, non-zero exit) — is a variant
//! of the single [`Error`] enum, so callers branch with one `match`. The crate
//! also locates and archives on-disk sessions by title ([`SessionStore`]). It
//! knows only about the Claude Code CLI — no application types, hard-coded
//! watermarks, or logging. Callers wire streaming output through a [`Sink`].
//!
//! Two layers:
//!
//! - [`Claude::run`] — the low-level driver: one turn, one [`Attach`] target.
//! - [`InfiniteSession`] — a durable session (name + [`SessionStore`] +
//! [`CompactionPolicy`]) that keeps itself alive across the context window by
//! compacting reactively (on overflow) and proactively (per policy — e.g.
//! [`PercentPolicy`]). This is the one you usually want.
//!
//! # `thiserror` here, `anyhow` in the apps
//!
//! This is a **library**, so it exposes a concrete, matchable error enum built
//! with [`thiserror`](https://docs.rs/thiserror): a caller can distinguish
//! `Error::Exit { status, .. }` from `Error::Spawn { .. }` and branch on it.
//! A library should never force its callers to reach into `anyhow`'s
//! type-erased error to find out what went wrong.
//!
//! The **applications** in this workspace (the `hive-*` binaries) use
//! [`anyhow`](https://docs.rs/anyhow) instead. At the top level you usually
//! only want to attach context and log or bubble a failure up — not match on
//! it — and `anyhow::Result` + `?` + `.context()` is the ergonomic fit.
//! `anyhow::Error` implements `From<E>` for any `std::error::Error`, so a
//! `hive_claude::Error` converts into an `anyhow::Error` for free at the `?`
//! boundary. Rule of thumb: **libraries return `thiserror` enums, binaries
//! consume them with `anyhow`.**
//!
//! # Example
//!
//! ```no_run
//! # async fn ex() {
//! use hive_claude::{Attach, Claude, Config, Error, NoopSink};
//!
//! let config = Config {
//! model: "haiku".into(),
//! ..Default::default()
//! };
//! match Claude::run(&config, &Attach::Resume("my-session".into()), "hello", &NoopSink).await {
//! Ok(()) => {}
//! Err(Error::PromptTooLong) => { /* caller compacts + retries */ }
//! Err(Error::RateLimited) => { /* caller parks + retries */ }
//! Err(other) => eprintln!("claude: {other}"),
//! }
//! # }
//! ```
// The crate-level `//!` overview above is the rustdoc entry point — it belongs
// in source, not docs/, so the comment-block length lint should not flag it.
// lint:allow-long-comment
mod classify;
mod config;
mod driver;
mod error;
mod policy;
mod session;
mod sink;
mod store;
mod telemetry;
pub use config::{Attach, Config};
pub use driver::Claude;
pub use error::{Error, Result};
pub use policy::{CompactionPolicy, NeverCompact, PercentPolicy};
pub use session::{InfiniteSession, Progress};
pub use sink::{NoopSink, Sink};
pub use store::SessionStore;
pub use telemetry::{Telemetry, TokenUsage, Usage};