Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-runtime/src/lib.rs
atlas 710f5b7b8c hive-runtime: re-export ACP_API_KEY_ENV_ENV
docs-rustdoc failed: the doc link on AcpCommand::api_key_env pointed at
ACP_API_KEY_ENV_ENV, which wasn't re-exported from lib.rs like its
sibling *_ENV consts, so the link resolved to a private item.
2026-09-30 22:55:03 +02:00

140 lines
4.7 KiB
Rust

//! `hive-runtime` — the layer an agent's turns are driven through, with one
//! backend per runtime:
//!
//! - [`ClaudeRuntime`] — `claude --print`, via `hive_claude`'s
//! [`hive_claude::InfiniteSession`];
//! - [`AcpRuntime`] — any Agent Client Protocol agent, spawned from the
//! command, args and env it is handed (see [`RuntimeSpec`]). It knows no
//! agent by name.
//!
//! Both report a turn through the same [`Sink`], in claude's `stream-json`
//! shape: the ACP backend translates its `session/update` notifications into
//! it, so a consumer of that stream reads either backend unchanged.
//!
//! Depends on no hyperhive binary crate, so the harness and the subagent
//! daemon can both drive turns through it.
use std::future::Future;
use std::path::PathBuf;
mod acp;
mod claude;
mod spec;
pub use acp::{
AcpError, AcpRuntime, Canceller, Choice, Choices, PermissionAsk, PermissionPolicy,
ReportedCost, SessionChoices,
};
pub use claude::ClaudeRuntime;
pub use hive_claude::{
CompactionPolicy, Config, PercentPolicy, Progress, SessionStore, Sink, Telemetry, TokenUsage,
};
pub use spec::{
ACP_API_KEY_ENV_ENV, ACP_ARGS_ENV, ACP_COMMAND_ENV, ACP_ENV_ENV, AcpCommand, RUNTIME_ENV,
RuntimeSpec, SpecError,
};
/// The operations every runtime backend offers.
///
/// Each takes the same [`Config`] the claude backend spawns from; a backend
/// reads the fields that mean something to it and ignores the rest.
pub trait Runtime {
/// Run one turn on the agent's durable session, creating it if there is
/// none, and stream it to `sink`.
fn run(
&self,
config: &Config,
prompt: &str,
sink: &impl Sink,
) -> impl Future<Output = Result<Progress>>;
/// Compact the durable session now. With no session there is nothing to
/// compact, and this succeeds without doing anything.
fn compact(&self, config: &Config, sink: &impl Sink) -> impl Future<Output = Result<()>>;
/// Set the durable session aside so the next turn starts a fresh one.
/// Returns the file the session was moved to, or `None` if there was no
/// session to archive. Only call it between turns.
fn archive(&self) -> Result<Option<PathBuf>>;
/// A handle that stops the turn in flight from outside [`Self::run`].
/// `None` for a runtime without one: a claude turn is stopped by
/// signalling its `claude` process.
fn canceller(&self) -> Option<Canceller>;
/// A handle reading the model and effort the session lets the client
/// pick. `None` for a runtime whose session offers none to read: claude's
/// `--model` and `--effort` are passed through unchecked.
fn choices(&self) -> Option<Choices>;
/// What the agent reported spending since the last call, one entry per
/// currency. Empty for a runtime whose agent reports no cost: claude
/// exports its own.
fn take_reported_cost(&self) -> Vec<ReportedCost>;
}
/// The runtime an agent was configured with, chosen at startup from a
/// [`RuntimeSpec`].
pub enum AgentRuntime<P: CompactionPolicy> {
Claude(ClaudeRuntime<P>),
Acp(Box<AcpRuntime<P>>),
}
impl<P: CompactionPolicy> Runtime for AgentRuntime<P> {
async fn run(&self, config: &Config, prompt: &str, sink: &impl Sink) -> Result<Progress> {
match self {
Self::Claude(r) => r.run(config, prompt, sink).await,
Self::Acp(r) => r.run(config, prompt, sink).await,
}
}
async fn compact(&self, config: &Config, sink: &impl Sink) -> Result<()> {
match self {
Self::Claude(r) => r.compact(config, sink).await,
Self::Acp(r) => r.compact(config, sink).await,
}
}
fn archive(&self) -> Result<Option<PathBuf>> {
match self {
Self::Claude(r) => r.archive(),
Self::Acp(r) => r.archive(),
}
}
fn canceller(&self) -> Option<Canceller> {
match self {
Self::Claude(r) => r.canceller(),
Self::Acp(r) => r.canceller(),
}
}
fn choices(&self) -> Option<Choices> {
match self {
Self::Claude(r) => r.choices(),
Self::Acp(r) => r.choices(),
}
}
fn take_reported_cost(&self) -> Vec<ReportedCost> {
match self {
Self::Claude(r) => r.take_reported_cost(),
Self::Acp(r) => r.take_reported_cost(),
}
}
}
/// Why a runtime operation did not complete.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// From the claude backend, unchanged.
#[error(transparent)]
Claude(#[from] hive_claude::Error),
/// From the ACP backend.
#[error(transparent)]
Acp(#[from] AcpError),
}
/// Result alias for this crate.
pub type Result<T> = std::result::Result<T, Error>;