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.
140 lines
4.7 KiB
Rust
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>;
|