feat(claude-cli): add local Claude Code CLI provider bridge

Spawn the local `claude` binary as a subprocess and expose it as an
Anthropic Messages-compatible provider. Hosted in brightstaff
(`CLAUDE_CLI_LISTEN_ADDR`), with session reuse, idle TTL, and watchdog.

User-facing surface is `model_providers: [{ model: claude-cli/* }]` —
the Python CLI auto-fills name/provider_interface/base_url/access_key
and the launcher (native + supervisord) enables the bridge listener
only when at least one claude-cli provider is present.
This commit is contained in:
Spherrrical 2026-05-04 12:57:53 -07:00
parent b71a555f19
commit 9fdfeb7cbf
26 changed files with 2847 additions and 2 deletions

View file

@ -400,6 +400,10 @@ pub enum LlmProviderType {
Vercel,
#[serde(rename = "openrouter")]
OpenRouter,
/// Claude Code CLI invoked as a local subprocess. The bridge runs inside
/// brightstaff (`CLAUDE_CLI_LISTEN_ADDR`) and exposes Anthropic Messages.
#[serde(rename = "claude-cli")]
ClaudeCli,
}
impl Display for LlmProviderType {
@ -425,6 +429,7 @@ impl Display for LlmProviderType {
LlmProviderType::DigitalOcean => write!(f, "digitalocean"),
LlmProviderType::Vercel => write!(f, "vercel"),
LlmProviderType::OpenRouter => write!(f, "openrouter"),
LlmProviderType::ClaudeCli => write!(f, "claude-cli"),
}
}
}
@ -772,6 +777,7 @@ mod test {
for (yaml_value, expected) in [
("vercel", LlmProviderType::Vercel),
("openrouter", LlmProviderType::OpenRouter),
("claude-cli", LlmProviderType::ClaudeCli),
] {
let parsed: LlmProviderType =
serde_yaml::from_str(yaml_value).expect("variant should deserialize");