hive-runtime: shared runtime crate with claude and acp backends
A `Runtime` trait (run / compact / archive) with two backends: - claude: a pass-through to hive_claude's InfiniteSession and SessionStore, so a claude turn is the same spawn, session handling and errors as before. - acp: a generic Agent Client Protocol client. It spawns the command, args and env from RuntimeSpec (HIVE_RUNTIME / HIVE_ACP_COMMAND / HIVE_ACP_ARGS / HIVE_ACP_ENV), refuses an agent whose mcpCapabilities.http is not true, passes the claude --mcp-config servers as ACP mcpServers, keeps one session id in a file (session/load after a restart, session/new otherwise), and maps session/update into claude stream-json events plus usage_update into Telemetry. Permission requests are answered by a caller-supplied policy on the ACP tool kind. compact returns Unsupported for now. The crate depends on no hyperhive binary crate, so the subagent daemon can move onto it without pulling in hive-agent. Refs #4391
This commit is contained in:
parent
d98d407bf1
commit
b0e26e7e44
10 changed files with 1583 additions and 0 deletions
103
hive-runtime/src/lib.rs
Normal file
103
hive-runtime/src/lib.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
//! `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, PermissionPolicy};
|
||||
pub use claude::ClaudeRuntime;
|
||||
pub use hive_claude::{
|
||||
CompactionPolicy, Config, PercentPolicy, Progress, SessionStore, Sink, Telemetry, TokenUsage,
|
||||
};
|
||||
pub use spec::{
|
||||
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. A backend that cannot returns
|
||||
/// [`Error::Unsupported`].
|
||||
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>>;
|
||||
}
|
||||
|
||||
/// The runtime an agent was configured with, chosen at startup from a
|
||||
/// [`RuntimeSpec`].
|
||||
pub enum AgentRuntime<P: CompactionPolicy> {
|
||||
Claude(ClaudeRuntime<P>),
|
||||
Acp(Box<AcpRuntime>),
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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),
|
||||
/// The backend does not implement this operation.
|
||||
#[error("{0} is not supported by this runtime")]
|
||||
Unsupported(&'static str),
|
||||
}
|
||||
|
||||
/// Result alias for this crate.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
Loading…
Reference in a new issue