use bitrouter_providers::oauth::credential_store::{Credential, CredentialStore, DEFAULT_LABEL};
use bitrouter_sdk::HeaderMap;
use bitrouter_sdk::PromptTransform;
use bitrouter_sdk::config::{Config, ProviderConfig};
use bitrouter_sdk::language_model::types::Prompt;
const PROVIDER_ID: &str = "claude-code";
pub struct ClaudeCodeRouter;
impl PromptTransform for ClaudeCodeRouter {
fn apply(&self, _prompt: &mut Prompt) {
}
fn apply_with_headers(&self, prompt: &mut Prompt, headers: &HeaderMap) {
let is_cc = headers_indicate_claude_code(headers);
if is_cc && prompt.model.starts_with("claude") && !prompt.model.starts_with("claude-code:")
{
prompt.model = format!("claude-code:{}", prompt.model);
}
}
}
fn headers_indicate_claude_code(headers: &HeaderMap) -> bool {
headers
.get_all("anthropic-beta")
.iter()
.filter_map(|v| v.to_str().ok())
.any(|v| v.split(',').any(|b| b.trim().starts_with("claude-code")))
}
pub fn enable_if_logged_in(config: &mut Config) {
migrate_legacy_anthropic_marker_default();
let Ok(store) = CredentialStore::default_path() else {
return;
};
enable_if_logged_in_with_store(config, &store);
}
const LEGACY_PROVIDER_ID: &str = "anthropic";
pub fn migrate_legacy_anthropic_marker_default() {
let Ok(store) = CredentialStore::default_path() else {
return;
};
let _ = migrate_legacy_anthropic_marker(store.path().to_path_buf());
}
pub fn migrate_legacy_anthropic_marker(store_path: std::path::PathBuf) -> bool {
let Ok(mut store) = CredentialStore::load(store_path) else {
return false;
};
let labels: Vec<String> = store
.labels(LEGACY_PROVIDER_ID)
.into_iter()
.map(str::to_string)
.collect();
let mut moved_any = false;
for label in labels {
if !matches!(
store.get_any(LEGACY_PROVIDER_ID, &label),
Some(Credential::ClaudeCodeCli)
) {
continue;
}
if store.get_any(PROVIDER_ID, &label).is_some() {
continue;
}
if store
.set(PROVIDER_ID, &label, Credential::ClaudeCodeCli)
.is_err()
{
continue;
}
let _ = store.remove(LEGACY_PROVIDER_ID, &label);
moved_any = true;
}
moved_any
}
fn enable_if_logged_in_with_store(config: &mut Config, store: &CredentialStore) {
if config.providers.contains_key(PROVIDER_ID) {
return;
}
let logged_in = !store.labels(PROVIDER_ID).is_empty()
|| store.get_any(PROVIDER_ID, DEFAULT_LABEL).is_some();
if !logged_in {
return;
}
config
.providers
.insert(PROVIDER_ID.to_string(), ProviderConfig::default());
}
#[cfg(test)]
mod tests {
use super::*;
use bitrouter_providers::oauth::credential_store::Credential;
use bitrouter_sdk::language_model::types::{GenerationParams, ProviderMetadata};
const CC_BETA: &str = "claude-code-20250219,oauth-2025-04-20,context-1m-2025-08-07";
fn prompt(model: &str) -> Prompt {
Prompt {
model: model.to_string(),
system: None,
system_provider_metadata: ProviderMetadata::new(),
messages: Vec::new(),
tools: Vec::new(),
params: GenerationParams::default(),
response_format: None,
tool_choice: None,
stream: false,
}
}
fn route(model: &str, anthropic_beta: Option<&str>) -> String {
let mut p = prompt(model);
let mut headers = HeaderMap::new();
if let Some(beta) = anthropic_beta {
headers.insert("anthropic-beta", beta.parse().unwrap());
}
ClaudeCodeRouter.apply_with_headers(&mut p, &headers);
p.model
}
#[test]
fn claude_code_beta_and_bare_claude_model_is_prefixed() {
assert_eq!(
route("claude-opus-4-8", Some(CC_BETA)),
"claude-code:claude-opus-4-8"
);
}
#[test]
fn claude_code_beta_and_non_claude_model_is_untouched() {
assert_eq!(route("gpt-5", Some(CC_BETA)), "gpt-5");
}
#[test]
fn no_claude_code_beta_leaves_claude_model_for_payg() {
assert_eq!(route("claude-opus-4-8", None), "claude-opus-4-8");
assert_eq!(
route(
"claude-opus-4-8",
Some("oauth-2025-04-20,context-1m-2025-08-07")
),
"claude-opus-4-8"
);
}
#[test]
fn already_prefixed_claude_model_is_untouched() {
assert_eq!(
route("claude-code:claude-opus-4-8", Some(CC_BETA)),
"claude-code:claude-opus-4-8"
);
}
#[test]
fn idempotent_on_already_claude_code_prefixed_model() {
let mut p = prompt("claude-opus-4-8");
let mut headers = HeaderMap::new();
headers.insert("anthropic-beta", CC_BETA.parse().unwrap());
ClaudeCodeRouter.apply_with_headers(&mut p, &headers);
ClaudeCodeRouter.apply_with_headers(&mut p, &headers);
assert_eq!(p.model, "claude-code:claude-opus-4-8");
}
#[test]
fn apply_without_headers_is_noop() {
let mut p = prompt("claude-opus-4-8");
ClaudeCodeRouter.apply(&mut p);
assert_eq!(p.model, "claude-opus-4-8");
}
fn fresh_tmp_store() -> CredentialStore {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
let dir = std::env::temp_dir().join(format!(
"bitrouter-cc-router-test-{}-{id}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
CredentialStore::load(dir.join("creds.json")).unwrap()
}
#[test]
fn enable_inserts_when_marker_present() {
let mut store = fresh_tmp_store();
store
.set(PROVIDER_ID, DEFAULT_LABEL, Credential::ClaudeCodeCli)
.unwrap();
let mut config = Config::default();
enable_if_logged_in_with_store(&mut config, &store);
assert!(
config.providers.contains_key(PROVIDER_ID),
"`claude-code` provider should be auto-enabled when a credential is stored"
);
}
#[test]
fn enable_noop_when_no_credential() {
let store = fresh_tmp_store();
let mut config = Config::default();
enable_if_logged_in_with_store(&mut config, &store);
assert!(
!config.providers.contains_key(PROVIDER_ID),
"no credential → no provider inserted"
);
}
#[test]
fn migration_moves_marker_from_anthropic_to_claude_code() {
let mut store = fresh_tmp_store();
let path = store.path().to_path_buf();
store
.set(LEGACY_PROVIDER_ID, DEFAULT_LABEL, Credential::ClaudeCodeCli)
.unwrap();
drop(store);
assert!(migrate_legacy_anthropic_marker(path.clone()));
let reloaded = CredentialStore::load(&path).unwrap();
assert!(
matches!(
reloaded.get_any(PROVIDER_ID, DEFAULT_LABEL),
Some(Credential::ClaudeCodeCli)
),
"the marker must now live under `claude-code`"
);
assert!(
reloaded
.get_any(LEGACY_PROVIDER_ID, DEFAULT_LABEL)
.is_none(),
"the legacy `anthropic` marker must be removed"
);
}
#[test]
fn migration_leaves_anthropic_api_key_untouched() {
let mut store = fresh_tmp_store();
let path = store.path().to_path_buf();
store
.set(
LEGACY_PROVIDER_ID,
DEFAULT_LABEL,
Credential::api_key("sk-ant-api03-platform"),
)
.unwrap();
drop(store);
assert!(!migrate_legacy_anthropic_marker(path.clone()));
let reloaded = CredentialStore::load(&path).unwrap();
assert_eq!(
reloaded
.get_any(LEGACY_PROVIDER_ID, DEFAULT_LABEL)
.and_then(Credential::as_api_key),
Some("sk-ant-api03-platform"),
"the anthropic platform API key must be left in place"
);
assert!(
reloaded.get_any(PROVIDER_ID, DEFAULT_LABEL).is_none(),
"no claude-code credential should be created for an API key"
);
}
#[test]
fn migration_does_not_clobber_existing_claude_code_credential() {
let mut store = fresh_tmp_store();
let path = store.path().to_path_buf();
store
.set(LEGACY_PROVIDER_ID, DEFAULT_LABEL, Credential::ClaudeCodeCli)
.unwrap();
store
.set(
PROVIDER_ID,
DEFAULT_LABEL,
Credential::api_key("sk-ant-oat-existing"),
)
.unwrap();
drop(store);
assert!(!migrate_legacy_anthropic_marker(path.clone()));
let reloaded = CredentialStore::load(&path).unwrap();
assert!(
matches!(
reloaded.get_any(LEGACY_PROVIDER_ID, DEFAULT_LABEL),
Some(Credential::ClaudeCodeCli)
),
"the anthropic marker must be left as-is when claude-code already has a credential"
);
assert_eq!(
reloaded
.get_any(PROVIDER_ID, DEFAULT_LABEL)
.and_then(Credential::as_api_key),
Some("sk-ant-oat-existing"),
"the pre-existing claude-code credential must not be clobbered"
);
}
#[test]
fn migration_noop_on_unreadable_store() {
let dir =
std::env::temp_dir().join(format!("bitrouter-cc-migrate-bad-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
assert!(!migrate_legacy_anthropic_marker(dir.clone()));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn enable_noop_when_already_present() {
let mut store = fresh_tmp_store();
store
.set(PROVIDER_ID, DEFAULT_LABEL, Credential::ClaudeCodeCli)
.unwrap();
let mut config = Config::default();
config.providers.insert(
PROVIDER_ID.to_string(),
ProviderConfig {
api_base: "https://example.invalid".to_string(),
..ProviderConfig::default()
},
);
enable_if_logged_in_with_store(&mut config, &store);
assert_eq!(
config.providers.get(PROVIDER_ID).unwrap().api_base,
"https://example.invalid",
"existing entry must not be overwritten"
);
}
}