refactor(agent): extract claude driver into hive-claude crate
This commit is contained in:
parent
e35acca814
commit
a3b66241d1
13 changed files with 907 additions and 442 deletions
61
hive-claude/README.md
Normal file
61
hive-claude/README.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# hive-claude
|
||||
|
||||
A small, reusable async driver for headless `claude --print` (Claude Code CLI)
|
||||
sessions. It spawns the CLI, streams and classifies the `stream-json` output,
|
||||
and reports the result. It knows only about the Claude Code CLI — **no
|
||||
hyperhive types, policy, watermarks, or logging.** Compaction, retry, and reset
|
||||
policy belong to the caller.
|
||||
|
||||
## When to use it
|
||||
|
||||
Reach for this crate whenever you need to run the `claude` CLI from Rust and
|
||||
react to how a turn ended. It is the shared substrate under
|
||||
`hive-ag3nt`'s turn loop; new callers (tools, tests, other agents) should build
|
||||
on it rather than shelling out to `claude` by hand.
|
||||
|
||||
## Shape
|
||||
|
||||
- **`Claude::run(&config, &session, prompt, &sink)`** → `Result<(), Error>`. A
|
||||
clean turn is `Ok(())`; every non-completion state is an [`Error`] variant, so
|
||||
you branch with a single `match`.
|
||||
- **`Claude::run_resume_or_create(&config, title, prompt, &sink)`** →
|
||||
`Result<bool>`. Resumes a titled session, creating it on first use.
|
||||
`Ok(true)` means a fresh session was minted.
|
||||
- **`Config`** — the invocation (model, effort, cwd, prompt/MCP files, tools,
|
||||
extra args). **`Session`** — which session to attach to (`Resume` / `Create`
|
||||
/ `Continue` / `OneOff`).
|
||||
- **`Sink`** — a trait with no-op defaults; implement the methods you care
|
||||
about to observe stream events, non-JSON stdout, and stderr. Use `NoopSink`
|
||||
when you only want the result.
|
||||
- **`SessionStore`** — locate and archive on-disk sessions by title.
|
||||
|
||||
`Error` unifies the two things that can stop a turn: recognized **sentinels**
|
||||
(`PromptTooLong`, `RateLimited`, `AuthFailed`, `SessionNotFound`) and **hard
|
||||
failures** (`Spawn`, `Stdin`, `Wait`, `Exit`, `Io`). Sentinels are expected
|
||||
control-flow, not crashes — the caller compacts, parks, re-auths, or creates a
|
||||
session in response.
|
||||
|
||||
```rust
|
||||
use hive_claude::{Claude, Config, Error, NoopSink, Session};
|
||||
|
||||
let config = Config { model: "haiku".into(), ..Default::default() };
|
||||
match Claude::run(&config, &Session::Resume("my-session".into()), "hello", &NoopSink).await {
|
||||
Ok(()) => {}
|
||||
Err(Error::PromptTooLong) => { /* compact + retry */ }
|
||||
Err(Error::RateLimited) => { /* park + retry */ }
|
||||
Err(other) => eprintln!("claude: {other}"),
|
||||
}
|
||||
```
|
||||
|
||||
## `thiserror` here, `anyhow` in the apps
|
||||
|
||||
This is a **library**, so it returns a concrete, matchable `Error` enum built
|
||||
with `thiserror`: callers can tell a rate-limit from a spawn failure and act
|
||||
accordingly. Libraries should never force their callers into `anyhow`'s
|
||||
type-erased error.
|
||||
|
||||
The **applications** (the `hive-*` binaries) use `anyhow` instead — at the top
|
||||
level you usually only want to add context and bubble a failure up, not match
|
||||
on it. 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`.**
|
||||
Loading…
Reference in a new issue