mirror of
https://github.com/lWolvesl/claw-code.git
synced 2026-04-02 07:41:52 +08:00
feat: make rusty-claude-cli usable end-to-end
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
This commit is contained in:
@@ -41,14 +41,12 @@ impl AnthropicClient {
|
||||
}
|
||||
|
||||
pub fn from_env() -> Result<Self, ApiError> {
|
||||
Ok(Self::new(read_api_key(|key| std::env::var(key))?)
|
||||
.with_auth_token(std::env::var("ANTHROPIC_AUTH_TOKEN").ok())
|
||||
.with_base_url(
|
||||
std::env::var("ANTHROPIC_BASE_URL")
|
||||
.ok()
|
||||
.or_else(|| std::env::var("CLAUDE_CODE_API_BASE_URL").ok())
|
||||
.unwrap_or_else(|| DEFAULT_BASE_URL.to_string()),
|
||||
))
|
||||
Ok(Self::new(read_api_key()?).with_base_url(
|
||||
std::env::var("ANTHROPIC_BASE_URL")
|
||||
.ok()
|
||||
.or_else(|| std::env::var("CLAUDE_CODE_API_BASE_URL").ok())
|
||||
.unwrap_or_else(|| DEFAULT_BASE_URL.to_string()),
|
||||
))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -187,13 +185,16 @@ impl AnthropicClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_api_key(
|
||||
getter: impl FnOnce(&str) -> Result<String, std::env::VarError>,
|
||||
) -> Result<String, ApiError> {
|
||||
match getter("ANTHROPIC_API_KEY") {
|
||||
Ok(api_key) if api_key.is_empty() => Err(ApiError::MissingApiKey),
|
||||
Ok(api_key) => Ok(api_key),
|
||||
Err(std::env::VarError::NotPresent) => Err(ApiError::MissingApiKey),
|
||||
fn read_api_key() -> Result<String, ApiError> {
|
||||
match std::env::var("ANTHROPIC_AUTH_TOKEN") {
|
||||
Ok(api_key) if !api_key.is_empty() => Ok(api_key),
|
||||
Ok(_) => Err(ApiError::MissingApiKey),
|
||||
Err(std::env::VarError::NotPresent) => match std::env::var("ANTHROPIC_API_KEY") {
|
||||
Ok(api_key) if !api_key.is_empty() => Ok(api_key),
|
||||
Ok(_) => Err(ApiError::MissingApiKey),
|
||||
Err(std::env::VarError::NotPresent) => Err(ApiError::MissingApiKey),
|
||||
Err(error) => Err(ApiError::from(error)),
|
||||
},
|
||||
Err(error) => Err(ApiError::from(error)),
|
||||
}
|
||||
}
|
||||
@@ -289,8 +290,6 @@ struct AnthropicErrorBody {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::env::VarError;
|
||||
|
||||
use super::{ALT_REQUEST_ID_HEADER, REQUEST_ID_HEADER};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -298,21 +297,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn read_api_key_requires_presence() {
|
||||
let error = super::read_api_key(|_| Err(VarError::NotPresent))
|
||||
.expect_err("missing key should error");
|
||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
let error = super::read_api_key().expect_err("missing key should error");
|
||||
assert!(matches!(error, crate::error::ApiError::MissingApiKey));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_api_key_requires_non_empty_value() {
|
||||
let error = super::read_api_key(|_| Ok(String::new())).expect_err("empty key should error");
|
||||
std::env::set_var("ANTHROPIC_AUTH_TOKEN", "");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
let error = super::read_api_key().expect_err("empty key should error");
|
||||
assert!(matches!(error, crate::error::ApiError::MissingApiKey));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_auth_token_drops_empty_values() {
|
||||
let client = super::AnthropicClient::new("test-key").with_auth_token(Some(String::new()));
|
||||
assert!(client.auth_token.is_none());
|
||||
fn read_api_key_prefers_auth_token() {
|
||||
std::env::set_var("ANTHROPIC_AUTH_TOKEN", "auth-token");
|
||||
std::env::set_var("ANTHROPIC_API_KEY", "legacy-key");
|
||||
assert_eq!(
|
||||
super::read_api_key().expect("token should load"),
|
||||
"auth-token"
|
||||
);
|
||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -50,11 +50,14 @@ impl Display for ApiError {
|
||||
Self::MissingApiKey => {
|
||||
write!(
|
||||
f,
|
||||
"ANTHROPIC_API_KEY is not set; export it before calling the Anthropic API"
|
||||
"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_API_KEY: {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}"),
|
||||
|
||||
@@ -178,6 +178,8 @@ mod tests {
|
||||
},
|
||||
usage: Usage {
|
||||
input_tokens: 1,
|
||||
cache_creation_input_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
output_tokens: 2,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -64,6 +64,11 @@ pub enum InputContentBlock {
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
ToolUse {
|
||||
id: String,
|
||||
name: String,
|
||||
input: Value,
|
||||
},
|
||||
ToolResult {
|
||||
tool_use_id: String,
|
||||
content: Vec<ToolResultContentBlock>,
|
||||
@@ -135,6 +140,10 @@ pub enum OutputContentBlock {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Usage {
|
||||
pub input_tokens: u32,
|
||||
#[serde(default)]
|
||||
pub cache_creation_input_tokens: u32,
|
||||
#[serde(default)]
|
||||
pub cache_read_input_tokens: u32,
|
||||
pub output_tokens: u32,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user