fix(onboard,anthropic): stabilize oauth setup-token flow and model defaults

- fix onboard command ownership handling before spawn_blocking

- restore memory helper imports in wizard to resolve build regression

- centralize Anthropic OAuth beta header in apply_auth for all request paths

- correct OpenRouter Anthropic Sonnet 4.5 model ID format

- add regression tests for auth headers and curated model IDs
This commit is contained in:
Chummy 2026-02-17 16:11:04 +08:00
parent bb6034e765
commit e197cc5b04
3 changed files with 72 additions and 4 deletions

View file

@ -367,8 +367,14 @@ async fn main() -> Result<()> {
api_key, api_key,
provider, provider,
memory, memory,
} = cli.command } = &cli.command
{ {
let interactive = *interactive;
let channels_only = *channels_only;
let api_key = api_key.clone();
let provider = provider.clone();
let memory = memory.clone();
if interactive && channels_only { if interactive && channels_only {
bail!("Use either --interactive or --channels-only, not both"); bail!("Use either --interactive or --channels-only, not both");
} }

View file

@ -467,7 +467,7 @@ fn default_model_for_provider(provider: &str) -> String {
"groq" => "llama-3.3-70b-versatile".into(), "groq" => "llama-3.3-70b-versatile".into(),
"deepseek" => "deepseek-chat".into(), "deepseek" => "deepseek-chat".into(),
"gemini" => "gemini-2.5-pro".into(), "gemini" => "gemini-2.5-pro".into(),
_ => "anthropic/claude-sonnet-4-5".into(), _ => "anthropic/claude-sonnet-4.5".into(),
} }
} }
@ -475,7 +475,7 @@ fn curated_models_for_provider(provider_name: &str) -> Vec<(String, String)> {
match canonical_provider_name(provider_name) { match canonical_provider_name(provider_name) {
"openrouter" => vec![ "openrouter" => vec![
( (
"anthropic/claude-sonnet-4-5".to_string(), "anthropic/claude-sonnet-4.5".to_string(),
"Claude Sonnet 4.5 (balanced, recommended)".to_string(), "Claude Sonnet 4.5 (balanced, recommended)".to_string(),
), ),
( (
@ -4345,6 +4345,16 @@ mod tests {
assert!(ids.contains(&"gpt-5-mini".to_string())); assert!(ids.contains(&"gpt-5-mini".to_string()));
} }
#[test]
fn curated_models_for_openrouter_use_valid_anthropic_id() {
let ids: Vec<String> = curated_models_for_provider("openrouter")
.into_iter()
.map(|(id, _)| id)
.collect();
assert!(ids.contains(&"anthropic/claude-sonnet-4.5".to_string()));
}
#[test] #[test]
fn supports_live_model_fetch_for_supported_and_unsupported_providers() { fn supports_live_model_fetch_for_supported_and_unsupported_providers() {
assert!(supports_live_model_fetch("openai")); assert!(supports_live_model_fetch("openai"));

View file

@ -139,7 +139,9 @@ impl AnthropicProvider {
credential: &str, credential: &str,
) -> reqwest::RequestBuilder { ) -> reqwest::RequestBuilder {
if Self::is_setup_token(credential) { if Self::is_setup_token(credential) {
request.header("Authorization", format!("Bearer {credential}")) request
.header("Authorization", format!("Bearer {credential}"))
.header("anthropic-beta", "oauth-2025-04-20")
} else { } else {
request.header("x-api-key", credential) request.header("x-api-key", credential)
} }
@ -474,6 +476,56 @@ mod tests {
assert!(!AnthropicProvider::is_setup_token("sk-ant-api-key")); assert!(!AnthropicProvider::is_setup_token("sk-ant-api-key"));
} }
#[test]
fn apply_auth_uses_bearer_and_beta_for_setup_tokens() {
let provider = AnthropicProvider::new(None);
let request = provider
.apply_auth(
provider.client.get("https://api.anthropic.com/v1/models"),
"sk-ant-oat01-test-token",
)
.build()
.expect("request should build");
assert_eq!(
request
.headers()
.get("authorization")
.and_then(|v| v.to_str().ok()),
Some("Bearer sk-ant-oat01-test-token")
);
assert_eq!(
request
.headers()
.get("anthropic-beta")
.and_then(|v| v.to_str().ok()),
Some("oauth-2025-04-20")
);
assert!(request.headers().get("x-api-key").is_none());
}
#[test]
fn apply_auth_uses_x_api_key_for_regular_tokens() {
let provider = AnthropicProvider::new(None);
let request = provider
.apply_auth(
provider.client.get("https://api.anthropic.com/v1/models"),
"sk-ant-api-key",
)
.build()
.expect("request should build");
assert_eq!(
request
.headers()
.get("x-api-key")
.and_then(|v| v.to_str().ok()),
Some("sk-ant-api-key")
);
assert!(request.headers().get("authorization").is_none());
assert!(request.headers().get("anthropic-beta").is_none());
}
#[tokio::test] #[tokio::test]
async fn chat_with_system_fails_without_key() { async fn chat_with_system_fails_without_key() {
let p = AnthropicProvider::new(None); let p = AnthropicProvider::new(None);