use super::{
ANTHROPIC_PROVIDER, AnthropicProvider, CLAUDE_SUBSCRIPTION_PROVIDER, OPENAI_CODEX_PROVIDER,
OpenAiCodexProvider, OpenAiCompatibleProvider, Provider, ProviderSelection,
ReqwestHttpTransport, claude_subscription::ClaudeSubscriptionProvider,
};
use crate::{
auth::ProviderCredential,
config::{CustomProviderConfig, EffectiveConfig, Settings},
fast::FastWorkload,
};
use anyhow::Result;
use std::{path::Path, sync::Arc};
pub(crate) fn supported_custom_provider<'a>(
config: &'a EffectiveConfig,
selection: &ProviderSelection,
) -> Result<Option<&'a CustomProviderConfig>> {
crate::config::reject_retired_provider_selection(&selection.provider, &selection.model)?;
let selected_custom_provider = config.custom_providers.get(&selection.provider);
if selection.provider.as_str() != OPENAI_CODEX_PROVIDER
&& selection.provider.as_str() != ANTHROPIC_PROVIDER
&& selection.provider.as_str() != CLAUDE_SUBSCRIPTION_PROVIDER
&& selected_custom_provider.is_none()
{
anyhow::bail!(
"unsupported provider '{}'; supported providers: '{}', '{}', '{}' and configured custom providers",
selection.provider,
OPENAI_CODEX_PROVIDER,
ANTHROPIC_PROVIDER,
CLAUDE_SUBSCRIPTION_PROVIDER
);
}
Ok(selected_custom_provider)
}
fn custom_provider_from_config<T>(
paths: &crate::config::McPaths,
provider_id: &str,
model: &str,
api_key: Option<String>,
custom: &CustomProviderConfig,
transport: T,
fast: crate::fast::FastRequestState,
) -> OpenAiCompatibleProvider<T> {
OpenAiCompatibleProvider::custom(
provider_id.to_string(),
model.to_string(),
api_key,
custom.base_url.clone(),
custom.use_responses_endpoint,
transport,
)
.with_request_headers(custom.request_headers.clone())
.with_text_verbosity_support(custom.supports_text_verbosity)
.with_reasoning_protocol(
custom.reasoning_protocol,
crate::model_catalog::cached_model_max_output_tokens(paths, provider_id, model),
)
.with_service_tier(fast.service_tier().map(str::to_string))
}
pub(crate) fn provider_from_selection_with_settings_for_workload(
config: &EffectiveConfig,
selection: &ProviderSelection,
cwd: &Path,
settings: &Settings,
workload: FastWorkload,
) -> Result<Arc<dyn Provider>> {
provider_from_selection_with_settings_for_workload_with_auth(
config,
selection,
cwd,
settings,
workload,
|config| config.resolve_provider_auth_for_runtime(),
)
}
pub(crate) fn provider_from_selection_with_settings_for_workload_with_resolved_auth(
config: &EffectiveConfig,
selection: &ProviderSelection,
cwd: &Path,
settings: &Settings,
workload: FastWorkload,
auth: ProviderCredential,
) -> Result<Arc<dyn Provider>> {
provider_from_selection_with_settings_for_workload_with_auth(
config,
selection,
cwd,
settings,
workload,
move |_| Ok(auth),
)
}
fn provider_from_selection_with_settings_for_workload_with_auth(
config: &EffectiveConfig,
selection: &ProviderSelection,
cwd: &Path,
settings: &Settings,
workload: FastWorkload,
resolve_auth: impl FnOnce(&EffectiveConfig) -> Result<ProviderCredential>,
) -> Result<Arc<dyn Provider>> {
let selected_custom_provider = supported_custom_provider(config, selection)?;
let fast = crate::fast::resolve_fast_capability(
settings,
&selection.provider,
&selection.model,
&config.paths,
selected_custom_provider,
workload,
);
let auth = resolve_auth(config)?;
match (selection.provider.as_str(), auth) {
(OPENAI_CODEX_PROVIDER, ProviderCredential::OAuth { access, account_id }) => {
let paths = config.paths.clone();
Ok(Arc::new(
OpenAiCodexProvider::new(
selection.model.clone(),
access,
account_id,
ReqwestHttpTransport,
)
.with_service_tier(fast.service_tier().map(str::to_string))
.with_store_auth_refresh(paths),
))
}
(OPENAI_CODEX_PROVIDER, ProviderCredential::ApiKey { .. }) => anyhow::bail!(
"provider 'openai-codex' requires provider-keyed OAuth auth; refusing to send API-key/runtime token to Codex transport"
),
(OPENAI_CODEX_PROVIDER, ProviderCredential::NoAuth) => anyhow::bail!(
"provider 'openai-codex' requires provider-keyed OAuth auth; refusing no-auth configuration"
),
(CLAUDE_SUBSCRIPTION_PROVIDER, ProviderCredential::NoAuth) => {
if let Err(reason) = crate::providers::claude_subscription::probe_cli_subscription() {
anyhow::bail!("Claude CLI subscription unavailable: {reason}");
}
Ok(Arc::new(ClaudeSubscriptionProvider {
cwd: cwd.to_path_buf(),
}))
}
(CLAUDE_SUBSCRIPTION_PROVIDER, _) => anyhow::bail!(
"Claude subscription requires CLI-managed claude.ai login; API keys and stored OAuth tokens are not used"
),
(ANTHROPIC_PROVIDER, ProviderCredential::ApiKey { key }) => {
let max_output_tokens = crate::model_catalog::cached_model_max_output_tokens(
&config.paths,
&selection.provider,
&selection.model,
);
let thinking_level = crate::thinking::resolve_thinking_level(
&crate::thinking::available_thinking_levels(
&selection.provider,
&selection.model,
crate::model_catalog::cached_model_thinking_metadata(
&config.paths,
&selection.provider,
&selection.model,
)
.as_ref(),
crate::thinking::ThinkingCapabilityScope::BuiltIn,
),
config.thinking_level,
);
Ok(Arc::new(
AnthropicProvider::new(selection.model.clone(), key, ReqwestHttpTransport)
.with_cache_ttl(settings.anthropic_cache_ttl)
.with_max_output_tokens(max_output_tokens)
.with_thinking_level(thinking_level),
))
}
(ANTHROPIC_PROVIDER, ProviderCredential::OAuth { .. }) => {
anyhow::bail!("provider 'anthropic' requires Anthropic API-key auth, not OAuth token")
}
(ANTHROPIC_PROVIDER, ProviderCredential::NoAuth) => anyhow::bail!(
"provider 'anthropic' requires Anthropic API-key auth; refusing no-auth configuration"
),
(provider_id, ProviderCredential::ApiKey { key }) => {
let Some(custom) = selected_custom_provider else {
unreachable!("provider support checked above")
};
Ok(Arc::new(custom_provider_from_config(
&config.paths,
provider_id,
&selection.model,
Some(key),
custom,
ReqwestHttpTransport,
fast.clone(),
)))
}
(provider_id, ProviderCredential::NoAuth) => {
let Some(custom) = selected_custom_provider else {
unreachable!("provider support checked above")
};
Ok(Arc::new(custom_provider_from_config(
&config.paths,
provider_id,
&selection.model,
None,
custom,
ReqwestHttpTransport,
fast,
)))
}
(provider_id, ProviderCredential::OAuth { .. }) => anyhow::bail!(
"provider '{provider_id}' requires custom-provider auth mode, not OAuth token"
),
}
}