mirror of
https://github.com/lWolvesl/claw-code.git
synced 2026-04-02 07:41:52 +08:00
Wire the CLI to the Anthropic client, runtime conversation loop, and MVP in-tree tool executor so prompt mode and the default REPL both execute real turns instead of scaffold-only commands. Constraint: Proxy auth uses ANTHROPIC_AUTH_TOKEN as the primary x-api-key source and may stream extra usage fields Constraint: Must preserve existing scaffold commands while enabling real prompt and REPL flows Rejected: Keep prompt mode on the old scaffold path | does not satisfy end-to-end CLI requirement Rejected: Depend solely on raw SSE message_stop from proxy | proxy/event differences required tolerant parsing plus fallback handling Confidence: medium Scope-risk: moderate Reversibility: clean Directive: Keep prompt mode tool-free unless the one-shot path is explicitly expanded and reverified against the proxy Tested: cargo test -p api; cargo test -p tools; cargo test -p runtime; cargo test -p rusty-claude-cli; cargo build; cargo run -p rusty-claude-cli -- prompt "say hello"; printf '/quit\n' | cargo run -p rusty-claude-cli -- Not-tested: Full interactive tool_use roundtrip against the proxy in REPL mode
124 lines
3.5 KiB
Rust
124 lines
3.5 KiB
Rust
use std::env::VarError;
|
|
use std::fmt::{Display, Formatter};
|
|
use std::time::Duration;
|
|
|
|
#[derive(Debug)]
|
|
pub enum ApiError {
|
|
MissingApiKey,
|
|
InvalidApiKeyEnv(VarError),
|
|
Http(reqwest::Error),
|
|
Io(std::io::Error),
|
|
Json(serde_json::Error),
|
|
Api {
|
|
status: reqwest::StatusCode,
|
|
error_type: Option<String>,
|
|
message: Option<String>,
|
|
body: String,
|
|
retryable: bool,
|
|
},
|
|
RetriesExhausted {
|
|
attempts: u32,
|
|
last_error: Box<ApiError>,
|
|
},
|
|
InvalidSseFrame(&'static str),
|
|
BackoffOverflow {
|
|
attempt: u32,
|
|
base_delay: Duration,
|
|
},
|
|
}
|
|
|
|
impl ApiError {
|
|
#[must_use]
|
|
pub fn is_retryable(&self) -> bool {
|
|
match self {
|
|
Self::Http(error) => error.is_connect() || error.is_timeout() || error.is_request(),
|
|
Self::Api { retryable, .. } => *retryable,
|
|
Self::RetriesExhausted { last_error, .. } => last_error.is_retryable(),
|
|
Self::MissingApiKey
|
|
| Self::InvalidApiKeyEnv(_)
|
|
| Self::Io(_)
|
|
| Self::Json(_)
|
|
| Self::InvalidSseFrame(_)
|
|
| Self::BackoffOverflow { .. } => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for ApiError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::MissingApiKey => {
|
|
write!(
|
|
f,
|
|
"ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY is not set; export one before calling the Anthropic API"
|
|
)
|
|
}
|
|
Self::InvalidApiKeyEnv(error) => {
|
|
write!(
|
|
f,
|
|
"failed to read ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY: {error}"
|
|
)
|
|
}
|
|
Self::Http(error) => write!(f, "http error: {error}"),
|
|
Self::Io(error) => write!(f, "io error: {error}"),
|
|
Self::Json(error) => write!(f, "json error: {error}"),
|
|
Self::Api {
|
|
status,
|
|
error_type,
|
|
message,
|
|
body,
|
|
..
|
|
} => match (error_type, message) {
|
|
(Some(error_type), Some(message)) => {
|
|
write!(
|
|
f,
|
|
"anthropic api returned {status} ({error_type}): {message}"
|
|
)
|
|
}
|
|
_ => write!(f, "anthropic api returned {status}: {body}"),
|
|
},
|
|
Self::RetriesExhausted {
|
|
attempts,
|
|
last_error,
|
|
} => write!(
|
|
f,
|
|
"anthropic api failed after {attempts} attempts: {last_error}"
|
|
),
|
|
Self::InvalidSseFrame(message) => write!(f, "invalid sse frame: {message}"),
|
|
Self::BackoffOverflow {
|
|
attempt,
|
|
base_delay,
|
|
} => write!(
|
|
f,
|
|
"retry backoff overflowed on attempt {attempt} with base delay {base_delay:?}"
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ApiError {}
|
|
|
|
impl From<reqwest::Error> for ApiError {
|
|
fn from(value: reqwest::Error) -> Self {
|
|
Self::Http(value)
|
|
}
|
|
}
|
|
|
|
impl From<std::io::Error> for ApiError {
|
|
fn from(value: std::io::Error) -> Self {
|
|
Self::Io(value)
|
|
}
|
|
}
|
|
|
|
impl From<serde_json::Error> for ApiError {
|
|
fn from(value: serde_json::Error) -> Self {
|
|
Self::Json(value)
|
|
}
|
|
}
|
|
|
|
impl From<VarError> for ApiError {
|
|
fn from(value: VarError) -> Self {
|
|
Self::InvalidApiKeyEnv(value)
|
|
}
|
|
}
|