Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
70 KiB
JSON
1 line
70 KiB
JSON
{"repo": "agtico/pfterminal", "files": {"codex-rs/model-provider-info/src/lib.rs": {"dc15684c44cd4db290de680013aebe05190f968e": {"t": 1786068332.2732475, "n": 69613, "h": "3dafefe4968f067e", "c": "//! Registry of model providers supported by Codex.\n//!\n//! Providers can be defined in two places:\n//! 1. Built-in defaults compiled into the binary so Codex works out-of-the-box.\n//! 2. User-defined entries inside `~/.codex/config.toml` under the `model_providers`\n//! key. These override or extend the defaults at runtime.\n\nuse codex_api::Provider as ApiProvider;\nuse codex_api::RetryConfig as ApiRetryConfig;\nuse codex_api::is_azure_responses_provider;\nuse codex_protocol::auth::AuthMode;\nuse codex_protocol::config_types::ModelProviderAuthInfo;\nuse codex_protocol::error::CodexErr;\nuse codex_protocol::error::EnvVarError;\nuse codex_protocol::error::Result as CodexResult;\nuse codex_utils_absolute_path::AbsolutePathBuf;\nuse http::HeaderMap;\nuse http::header::HeaderName;\nuse http::header::HeaderValue;\nuse schemars::JsonSchema;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse serde_json::Value;\nuse std::collections::HashMap;\nuse std::fmt;\nuse std::num::NonZeroU64;\nuse std::time::Duration;\n\nconst DEFAULT_ANTHROPIC_REQUEST_BODY_MAX_BYTES: usize = 30_000_000;\nconst DEFAULT_ANTHROPIC_RETRY_BODY_MAX_BYTES: usize = 15_000_000;\n\nconst DEFAULT_STREAM_IDLE_TIMEOUT_MS: u64 = 600_000;\nconst DEFAULT_STREAM_ACTIONABLE_TIMEOUT_MS: u64 = 180_000;\nconst DEFAULT_STREAM_LONG_FAILURE_RETRY_THRESHOLD_MS: u64 = 60_000;\nconst DEFAULT_STREAM_LONG_FAILURE_MAX_RETRIES: u64 = 1;\nconst DEFAULT_STREAM_MAX_RETRIES: u64 = 5;\nconst DEFAULT_REQUEST_MAX_RETRIES: u64 = 4;\npub const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS: u64 = 15_000;\n/// Hard cap for user-configured `stream_max_retries`.\nconst MAX_STREAM_MAX_RETRIES: u64 = 100;\n/// Hard cap for user-configured `request_max_retries`.\nconst MAX_REQUEST_MAX_RETRIES: u64 = 100;\n\nfn provider_auth_timeout_ms() -> NonZeroU64 {\n match NonZeroU64::new(5_000) {\n Some(timeout_ms) => timeout_ms,\n None => panic!(\"provider auth timeout must be non-zero\"),\n }\n}\n\nfn root_absolute_path() -> AbsolutePathBuf {\n let current_dir = AbsolutePathBuf::current_dir().unwrap_or_else(|err| {\n panic!(\"current directory must resolve to determine provider auth cwd: {err}\")\n });\n current_dir\n .ancestors()\n .last()\n .unwrap_or_else(|| panic!(\"current directory must have a filesystem root\"))\n}\n\nconst OPENAI_PROVIDER_NAME: &str = \"OpenAI\";\nconst OPENAI_ACTOR_AUTHORIZATION_HEADER: &str = \"x-openai-actor-authorization\";\npub const OPENAI_PROVIDER_ID: &str = \"openai\";\n/// OpenAI backend compatibility version for protocol-gated model access.\n///\n/// This is intentionally separate from PFTerminal's product version. The OpenAI\n/// provider sends this value to first-party endpoints so fork-specific release\n/// numbering does not make the backend treat a compatible client as obsolete.\npub const OPENAI_CODEX_COMPAT_VERSION: &str = \"0.144.1\";\npub const CHATGPT_CODEX_BASE_URL: &str = \"https://chatgpt.com/backend-api/codex\";\nconst ANTHROPIC_PROVIDER_NAME: &str = \"Anthropic\";\npub const ANTHROPIC_PROVIDER_ID: &str = \"anthropic\";\npub const ANTHROPIC_BASE_URL: &str = \"https://api.anthropic.com/v1\";\npub const ANTHROPIC_DEFAULT_MODEL: &str = \"claude-opus-5\";\npub const ANTHROPIC_LEGACY_OPUS_4_8_MODEL: &str = \"claude-opus-4-8\";\npub const CLAUDE_FABLE_5_MODEL: &str = \"claude-fable-5\";\npub const ANTHROPIC_API_KEY_ENV_VAR: &str = \"ANTHROPIC_API_KEY\";\nconst CLAUDE_PLAN_PROVIDER_NAME: &str = \"Claude Plan\";\npub const CLAUDE_PLAN_PROVIDER_ID: &str = \"claude-plan\";\npub const CLAUDE_PLAN_MODEL: &str = \"claude-opus-5-plan\";\npub const CLAUDE_PLAN_UPSTREAM_MODEL: &str = ANTHROPIC_DEFAULT_MODEL;\npub const CLAUDE_PLAN_LEGACY_OPUS_4_8_MODEL: &str = \"claude-opus-4-8-plan\";\npub const CLAUDE_FABLE_5_PLAN_MODEL: &str = \"claude-fable-5-plan\";\npub const CLAUDE_FABLE_5_PLAN_UPSTREAM_MODEL: &str = CLAUDE_FABLE_5_MODEL;\nconst AMBIENT_PROVIDER_NAME: &str = \"Ambient\";\npub const AMBIENT_PROVIDER_ID: &str = \"ambient\";\npub const AMBIENT_BASE_URL: &str = \"https://api.ambient.xyz/v1\";\npub const AMBIENT_DEFAULT_MODEL: &str = \"z-ai/glm-5.2\";\n/// Input context accepted by Ambient's GLM 5.2 chat route.\n///\n/// The model can have a larger native context on other providers, so this\n/// limit belongs to the provider/model route rather than the shared model\n/// catalog.\npub const AMBIENT_GLM_5_2_CONTEXT_WINDOW: i64 = 101_376;\npub const AMBIENT_LEGACY_GLM_5_2_FP8_MODEL: &str = \"zai-org/GLM-5.2-FP8\";\npub const AMBIENT_KIMI_K2_7_CODE_MODEL: &str = \"moonshotai/kimi-k2.7-code\";\npub const AMBIENT_API_KEY_ENV_VAR: &str = \"AMBIENT_API_KEY\";\nconst PFTERMINAL_PLAN_PROVIDER_NAME: &str = \"PfTerminal Plan\";\npub const PFTERMINAL_PLAN_PROVIDER_ID: &str = \"pfterminal-plan\";\npub const PFTERMINAL_PLAN_GATEWAY_ORIGIN: &str = \"https://pfterminal-plan-gateway.fly.dev\";\npub const PFTERMINAL_PLAN_DEFAULT_BASE_URL: &str = \"https://pfterminal-plan-gateway.fly.dev/v1\";\npub const PFTERMINAL_PLAN_API_KEY_ENV_VAR: &str = \"PFTERMINAL_PLAN_API_KEY\";\nconst KIMI_CODE_PROVIDER_NAME: &str = \"Kimi Code\";\npub const KIMI_CODE_PROVIDER_ID: &str = \"kimi-code\";\npub const KIMI_CODE_BASE_URL: &str = \"https://api.kimi.com/coding/v1\";\npub const KIMI_CODE_K3_MODEL: &str = \"k3\";\npub const KIMI_CODE_API_KEY_ENV_VAR: &str = \"KIMI_API_KEY\";\nconst ZAI_PROVIDER_NAME: &str = \"Z.AI\";\npub const ZAI_PROVIDER_ID: &str = \"zai\";\npub const ZAI_BASE_URL: &str = \"https://api.z.ai/api/coding/paas/v4\";\nconst ZAI_ANTHROPIC_PROVIDER_NAME: &str = \"Z.AI Anthropic\";\npub const ZAI_ANTHROPIC_PROVIDER_ID: &str = \"zai-anthropic\";\npub const ZAI_ANTHROPIC_BASE_URL: &str = \"https://api.z.ai/api/anthropic/v1\";\npub const ZAI_DEFAULT_MODEL: &str = \"glm-5.2\";\npub const ZAI_API_KEY_ENV_VAR: &str = \"ZAI_API_KEY\";\nconst OPENROUTER_PROVIDER_NAME: &str = \"OpenRouter\";\npub const OPENROUTER_PROVIDER_ID: &str = \"openrouter\";\npub const OPENROUTER_BASE_URL: &str = \"https://openrouter.ai/api/v1\";\nconst OPENROUTER_ANTHROPIC_PROVIDER_NAME: &str = \"OpenRouter Anthropic\";\npub const OPENROUTER_ANTHROPIC_PROVIDER_ID: &str = \"openrouter-anthropic\";\npub const OPENROUTER_DEFAULT_MODEL: &str = \"z-ai/glm-5.2\";\npub const OPENROUTER_API_KEY_ENV_VAR: &str = \"OPENROUTER_API_KEY\";\nconst DEEPSEEK_PROVIDER_NAME: &str = \"DeepSeek\";\npub const DEEPSEEK_PROVIDER_ID: &str = \"deepseek\";\npub const DEEPSEEK_BASE_URL: &str = \"https://api.deepseek.com\";\npub const DEEPSEEK_DEFAULT_MODEL: &str = \"deepseek-v4-flash\";\npub const DEEPSEEK_API_KEY_ENV_VAR: &str = \"DEEPSEEK_API_KEY\";\nconst META_PROVIDER_NAME: &str = \"Meta\";\npub const META_PROVIDER_ID: &str = \"meta\";\npub const META_BASE_URL: &str = \"https://api.meta.ai/v1\";\npub const META_DEFAULT_MODEL: &str = \"muse-spark-1.1\";\npub const META_API_KEY_ENV_VAR: &str = \"MODEL_API_KEY\";\nconst BASETEN_PROVIDER_NAME: &str = \"Baseten\";\npub const BASETEN_PROVIDER_ID: &str = \"baseten\";\npub const BASETEN_BASE_URL: &str = \"https://inference.baseten.co/v1\";\nconst BASETEN_ANTHROPIC_PROVIDER_NAME: &str = \"Baseten Anthropic\";\npub const BASETEN_ANTHROPIC_PROVIDER_ID: &str = \"baseten-anthropic\";\npub const BASETEN_DEFAULT_MODEL: &str = \"zai-org/GLM-5.2\";\npub const BASETEN_API_KEY_ENV_VAR: &str = \"BASETEN_API_KEY\";\nconst VERCEL_PROVIDER_NAME: &str = \"Vercel\";\npub const VERCEL_PROVIDER_ID: &str = \"vercel\";\npub const VERCEL_BASE_URL: &str = \"https://ai-gateway.vercel.sh/v1\";\nconst VERCEL_GATEWAY_HOST: &str = \"ai-gateway.vercel.sh\";\n\n/// Host-only comparison: the gateway is reachable under several path prefixes\n/// (`/v1`, `/v1/messages`, `/v1/responses`) and with or without a trailing\n/// slash, and none of that changes the routing behaviour we need to correct.\nfn is_vercel_gateway_base_url(base_url: &str) -> bool {\n let without_scheme = base_url\n .trim()\n .split_once(\"://\")\n .map_or(base_url.trim(), |(_, rest)| rest);\n let host = without_scheme\n .split(['/', '?', '#'])\n .next()\n .unwrap_or_default();\n let host = host.rsplit_once('@').map_or(host, |(_, rest)| rest);\n let host = host.split_once(':').map_or(host, |(host, _)| host);\n host.eq_ignore_ascii_case(VERCEL_GATEWAY_HOST)\n}\nconst VERCEL_ANTHROPIC_PROVIDER_NAME: &str = \"Vercel Anthropic\";\npub const VERCEL_ANTHROPIC_PROVIDER_ID: &str = \"vercel-anthropic\";\nconst VERCEL_ANTHROPIC_FAST_PROVIDER_NAME: &str = \"Vercel Anthropic Fast\";\npub const VERCEL_ANTHROPIC_FAST_PROVIDER_ID: &str = \"vercel-anthropic-fast\";\npub const VERCEL_DEFAULT_MODEL: &str = \"zai/glm-5.2\";\npub const VERCEL_GLM_5_2_FAST_MODEL: &str = \"zai/glm-5.2-fast\";\npub const VERCEL_API_KEY_ENV_VAR: &str = \"AI_GATEWAY_API_KEY\";\n\n/// Built-in catalog providers eligible for impossible-pair correction. User-defined providers\n/// (e.g. a private Azure deployment) are never second-guessed.\nconst PAIR_CORRECTION_KNOWN_PROVIDERS: [&str; 18] = [\n OPENAI_PROVIDER_ID,\n ANTHROPIC_PROVIDER_ID,\n CLAUDE_PLAN_PROVIDER_ID,\n AMBIENT_PROVIDER_ID,\n PFTERMINAL_PLAN_PROVIDER_ID,\n KIMI_CODE_PROVIDER_ID,\n ZAI_PROVIDER_ID,\n ZAI_ANTHROPIC_PROVIDER_ID,\n OPENROUTER_PROVIDER_ID,\n OPENROUTER_ANTHROPIC_PROVIDER_ID,\n DEEPSEEK_PROVIDER_ID,\n META_PROVIDER_ID,\n BASETEN_PROVIDER_ID,\n BASETEN_ANTHROPIC_PROVIDER_ID,\n VERCEL_PROVIDER_ID,\n VERCEL_ANTHROPIC_PROVIDER_ID,\n VERCEL_ANTHROPIC_FAST_PROVIDER_ID,\n AMAZON_BEDROCK_PROVIDER_ID,\n];\n\nconst VERCEL_FAMILY_PROVIDERS: [&str; 3] = [\n VERCEL_PROVIDER_ID,\n VERCEL_ANTHROPIC_PROVIDER_ID,\n VERCEL_ANTHROPIC_FAST_PROVIDER_ID,\n];\n\n/// Return the canonical built-in provider for a picker-visible model.\n///\n/// This is a catalog ownership mapping, not an exclusivity claim: gateways and\n/// user-defined providers may also serve the same model. Callers use it when\n/// they must present an exact, known-good provider/model pair instead of\n/// forcing a model to guess a provider identifier from a display name.\npub fn canonical_catalog_provider(model: &str) -> Option<&'static str> {\n let model = model.trim();\n if model.is_empty() {\n return None;\n }\n if model == BASETEN_DEFAULT_MODEL {\n return Some(BASETEN_PROVIDER_ID);\n }\n if model == AMBIENT_DEFAULT_MODEL\n || model == AMBIENT_KIMI_K2_7_CODE_MODEL\n || model.starts_with(\"ambient/\")\n || model.starts_with(\"zai-org/\")\n {\n return Some(AMBIENT_PROVIDER_ID);\n }\n if model == KIMI_CODE_K3_MODEL {\n return Some(KIMI_CODE_PROVIDER_ID);\n }\n if model == ZAI_DEFAULT_MODEL || model.starts_with(\"glm-\") {\n return Some(ZAI_PROVIDER_ID);\n }\n if matches!(\n model,\n CLAUDE_PLAN_MODEL | CLAUDE_PLAN_LEGACY_OPUS_4_8_MODEL | CLAUDE_FABLE_5_PLAN_MODEL\n ) {\n return Some(CLAUDE_PLAN_PROVIDER_ID);\n }\n if model == ANTHROPIC_DEFAULT_MODEL || model.starts_with(\"claude-\") {\n return Some(ANTHROPIC_PROVIDER_ID);\n }\n if model == META_DEFAULT_MODEL {\n return Some(META_PROVIDER_ID);\n }\n if model == DEEPSEEK_DEFAULT_MODEL {\n return Some(DEEPSEEK_PROVIDER_ID);\n }\n if matches!(\n model,\n OPENROUTER_DEFAULT_MODEL\n | \"minimax/minimax-m3\"\n | \"openrouter/owl-alpha\"\n | \"google/gemini-3.5-flash\"\n | \"x-ai/grok-4.5\"\n | \"deepseek/deepseek-v4-pro\"\n | \"deepseek/deepseek-v4-flash-0731\"\n | \"tencent/hy3:free\"\n | \"moonshotai/kimi-k3\"\n ) {\n return Some(OPENROUTER_PROVIDER_ID);\n }\n if model == VERCEL_GLM_5_2_FAST_MODEL {\n return Some(VERCEL_ANTHROPIC_FAST_PROVIDER_ID);\n }\n if model == VERCEL_DEFAULT_MODEL {\n return Some(VERCEL_PROVIDER_ID);\n }\n if matches!(\n model,\n AMAZON_BEDROCK_GPT_5_5_MODEL_ID | AMAZON_BEDROCK_GPT_5_4_MODEL_ID\n ) {\n return Some(AMAZON_BEDROCK_PROVIDER_ID);\n }\n if model.starts_with(\"gpt-\") || model.starts_with(\"codex-auto-\") {\n return Some(OPENAI_PROVIDER_ID);\n }\n None\n}\n\n/// Returns the provider a session must use when its (model, provider) pair is impossible — the\n/// model belongs to a specific catalog provider family and the given provider is a different\n/// catalog provider that cannot serve it. Pairs go stale when thread metadata loses the provider\n/// and a fallback (config default, parent provider) is recorded next to a role- or\n/// rollout-derived model; running such a turn 400s/404s at the remote (\"Unknown model\").\n///\n/// Only unambiguous families are corrected:\n/// - `zai/…` slugs (Vercel gateway GLM ids) off the Vercel provider family;\n/// - the Claude plan models off `claude-plan`;\n/// - bare `glm-…` slugs (Z.AI-direct ids) off either Z.AI dialect;\n/// - the bare `k3` subscription model off Kimi Code;\n/// - the bare DeepSeek Responses model off the direct DeepSeek provider;\n/// - bare `gpt-…` slugs off OpenAI (Bedrock uses `openai.gpt-…` ids).\n///\n/// Servable-but-unusual pairs (e.g. ambient serving `z-ai/glm-5.2`), unknown models, and\n/// user-defined providers return `None` so intentional setups keep working.\npub fn corrected_catalog_provider(model: &str, provider: &str) -> Option<&'static str> {\n let model = model.trim();\n let provider = provider.trim();\n if model.is_empty()\n || provider.is_empty()\n || !PAIR_CORRECTION_KNOWN_PROVIDERS.contains(&provider)\n {\n return None;\n }\n if model.starts_with(\"zai/\") && !VERCEL_FAMILY_PROVIDERS.contains(&provider) {\n return Some(VERCEL_ANTHROPIC_FAST_PROVIDER_ID);\n }\n if matches!(\n model,\n CLAUDE_PLAN_MODEL | CLAUDE_PLAN_LEGACY_OPUS_4_8_MODEL | CLAUDE_FABLE_5_PLAN_MODEL\n ) && provider != CLAUDE_PLAN_PROVIDER_ID\n {\n return Some(CLAUDE_PLAN_PROVIDER_ID);\n }\n if model.starts_with(\"glm-\")\n && provider != ZAI_PROVIDER_ID\n && provider != ZAI_ANTHROPIC_PROVIDER_ID\n {\n return Some(ZAI_PROVIDER_ID);\n }\n if model == KIMI_CODE_K3_MODEL && provider != KIMI_CODE_PROVIDER_ID {\n return Some(KIMI_CODE_PROVIDER_ID);\n }\n if model == DEEPSEEK_DEFAULT_MODEL && provider != DEEPSEEK_PROVIDER_ID {\n return Some(DEEPSEEK_PROVIDER_ID);\n }\n if model.starts_with(\"gpt-\") && provider != OPENAI_PROVIDER_ID {\n return Some(OPENAI_PROVIDER_ID);\n }\n None\n}\n\npub fn resolve_model_for_provider(\n model: Option<String>,\n model_provider_id: &str,\n) -> Option<String> {\n match model_provider_id {\n AMBIENT_PROVIDER_ID | PFTERMINAL_PLAN_PROVIDER_ID => match model {\n Some(model) if model.trim() == AMBIENT_LEGACY_GLM_5_2_FP8_MODEL => {\n Some(AMBIENT_DEFAULT_MODEL.to_string())\n }\n Some(model)\n if model.trim().starts_with(\"ambient/\")\n || model.trim().starts_with(\"z-ai/\")\n || model.trim() == AMBIENT_KIMI_K2_7_CODE_MODEL\n || (model.trim().starts_with(\"zai-org/\")\n && model.trim() != BASETEN_DEFAULT_MODEL) =>\n {\n Some(model)\n }\n _ => Some(AMBIENT_DEFAULT_MODEL.to_string()),\n },\n KIMI_CODE_PROVIDER_ID => match model {\n Some(model) if model.trim() == KIMI_CODE_K3_MODEL => Some(model),\n _ => Some(KIMI_CODE_K3_MODEL.to_string()),\n },\n ZAI_PROVIDER_ID | ZAI_ANTHROPIC_PROVIDER_ID => match model {\n Some(model) if model.trim().starts_with(\"glm-\") => Some(model),\n _ => Some(ZAI_DEFAULT_MODEL.to_string()),\n },\n ANTHROPIC_PROVIDER_ID => match model {\n Some(model)\n if model.trim().starts_with(\"claude-\")\n && model.trim() != CLAUDE_PLAN_MODEL\n && model.trim() != CLAUDE_PLAN_LEGACY_OPUS_4_8_MODEL\n && model.trim() != CLAUDE_FABLE_5_PLAN_MODEL =>\n {\n Some(model)\n }\n _ => Some(ANTHROPIC_DEFAULT_MODEL.to_string()),\n },\n CLAUDE_PLAN_PROVIDER_ID => match model {\n Some(model)\n if matches!(\n model.trim(),\n CLAUDE_PLAN_MODEL\n | CLAUDE_PLAN_LEGACY_OPUS_4_8_MODEL\n | CLAUDE_FABLE_5_PLAN_MODEL\n ) =>\n {\n Some(model)\n }\n _ => Some(CLAUDE_PLAN_MODEL.to_string()),\n },\n OPENROUTER_PROVIDER_ID | OPENROUTER_ANTHROPIC_PROVIDER_ID => match model {\n Some(model) if !model.trim().is_empty() => Some(model),\n _ => Some(OPENROUTER_DEFAULT_MODEL.to_string()),\n },\n DEEPSEEK_PROVIDER_ID => match model {\n Some(model) if model.trim() == DEEPSEEK_DEFAULT_MODEL => Some(model),\n _ => Some(DEEPSEEK_DEFAULT_MODEL.to_string()),\n },\n META_PROVIDER_ID => match model {\n Some(model) if model.trim() == META_DEFAULT_MODEL => Some(model),\n _ => Some(META_DEFAULT_MODEL.to_string()),\n },\n BASETEN_PROVIDER_ID | BASETEN_ANTHROPIC_PROVIDER_ID => match model {\n Some(model) if model.trim() == BASETEN_DEFAULT_MODEL => Some(model),\n _ => Some(BASETEN_DEFAULT_MODEL.to_string()),\n },\n VERCEL_PROVIDER_ID | VERCEL_ANTHROPIC_PROVIDER_ID => match model {\n Some(model)\n if matches!(\n model.trim(),\n VERCEL_DEFAULT_MODEL | VERCEL_GLM_5_2_FAST_MODEL\n ) =>\n {\n Some(model)\n }\n _ => Some(VERCEL_DEFAULT_MODEL.to_string()),\n },\n VERCEL_ANTHROPIC_FAST_PROVIDER_ID => match model {\n Some(model)\n if matches!(\n model.trim(),\n VERCEL_DEFAULT_MODEL | VERCEL_GLM_5_2_FAST_MODEL\n ) =>\n {\n Some(model)\n }\n _ => Some(VERCEL_GLM_5_2_FAST_MODEL.to_string()),\n },\n _ => model,\n }\n}\n\n/// Return a provider-route context ceiling when it is narrower than the\n/// model's shared catalog capability.\npub fn default_model_context_window_for_provider(\n model_provider_id: &str,\n model: &str,\n) -> Option<i64> {\n match (model_provider_id, model.trim()) {\n (AMBIENT_PROVIDER_ID | PFTERMINAL_PLAN_PROVIDER_ID, AMBIENT_DEFAULT_MODEL) => {\n Some(AMBIENT_GLM_5_2_CONTEXT_WINDOW)\n }\n _ => None,\n }\n}\n\nfn provider_api_key_vault_instructions() -> String {\n [\n \"Run `/providers` and select the matching provider key:\",\n \"\",\n \"Providers\",\n \" Add or replace provider API keys. Keys are stored in the vault.\",\n \"\",\n \" Search providers\",\n \"> Provider: Anthropic API Key Store ANTHROPIC_API_KEY in the vault\",\n \" Provider: Ambient API Key Store AMBIENT_API_KEY in the vault\",\n \" Provider: Kimi Code API Key Store KIMI_API_KEY in the vault\",\n \" Provider: Z.AI API Key Store ZAI_API_KEY in the vault\",\n \" Provider: DeepSeek API Key Store DEEPSEEK_API_KEY in the vault\",\n \" Provider: OpenRouter API Key Store OPENROUTER_API_KEY in the vault\",\n \" Provider: Meta API Key Store MODEL_API_KEY in the vault\",\n \" Provider: Baseten API Key Store BASETEN_API_KEY in the vault\",\n \" Provider: Vercel API Key Store AI_GATEWAY_API_KEY in the vault\",\n ]\n .join(\"\\n\")\n}\n\nconst AMAZON_BEDROCK_PROVIDER_NAME: &str = \"Amazon Bedrock\";\npub const AMAZON_BEDROCK_PROVIDER_ID: &str = \"amazon-bedrock\";\npub const AMAZON_BEDROCK_GPT_5_5_MODEL_ID: &str = \"openai.gpt-5.5\";\npub const AMAZON_BEDROCK_GPT_5_4_MODEL_ID: &str = \"openai.gpt-5.4\";\npub const AMAZON_BEDROCK_GPT_5_6_SOL_MODEL_ID: &str = \"openai.gpt-5.6-sol\";\npub const AMAZON_BEDROCK_GPT_5_6_TERRA_MODEL_ID: &str = \"openai.gpt-5.6-terra\";\npub const AMAZON_BEDROCK_GPT_5_6_LUNA_MODEL_ID: &str = \"openai.gpt-5.6-luna\";\npub const AMAZON_BEDROCK_DEFAULT_BASE_URL: &str =\n \"https://bedrock-mantle.us-east-1.api.aws/openai/v1\";\nconst AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_HEADER: &str = \"x-amzn-mantle-client-agent\";\nconst AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_VALUE: &str = \"codex\";\npub const LEGACY_OLLAMA_CHAT_PROVIDER_ID: &str = \"ollama-chat\";\npub const OLLAMA_CHAT_PROVIDER_REMOVED_ERROR: &str = \"`ollama-chat` is no longer supported.\\nHow to fix: replace `ollama-chat` with `ollama` in `model_provider`, `oss_provider`, or `--local-provider`.\\nMore info: https://github.com/openai/codex/discussions/7782\";\nconst OSS_PROVIDER_NAME: &str = \"gpt-oss\";\npub const BUILT_IN_MODEL_PROVIDER_NAMES: [&str; 16] = [\n OPENAI_PROVIDER_NAME,\n ANTHROPIC_PROVIDER_NAME,\n CLAUDE_PLAN_PROVIDER_NAME,\n AMBIENT_PROVIDER_NAME,\n KIMI_CODE_PROVIDER_NAME,\n ZAI_PROVIDER_NAME,\n ZAI_ANTHROPIC_PROVIDER_NAME,\n DEEPSEEK_PROVIDER_NAME,\n OPENROUTER_PROVIDER_NAME,\n BASETEN_PROVIDER_NAME,\n BASETEN_ANTHROPIC_PROVIDER_NAME,\n VERCEL_PROVIDER_NAME,\n VERCEL_ANTHROPIC_PROVIDER_NAME,\n VERCEL_ANTHROPIC_FAST_PROVIDER_NAME,\n AMAZON_BEDROCK_PROVIDER_NAME,\n OSS_PROVIDER_NAME,\n];\n\n/// Wire protocol that the provider speaks.\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]\n#[serde(rename_all = \"lowercase\")]\npub enum WireApi {\n /// The Responses API exposed by OpenAI at `/v1/responses`.\n #[default]\n Responses,\n /// The OpenAI-compatible Chat Completions API exposed at `/v1/chat/completions`.\n Chat,\n /// The Anthropic-compatible Messages API exposed at `/v1/messages`.\n Anthropic,\n}\n\nimpl fmt::Display for WireApi {\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n let value = match self {\n Self::Responses => \"responses\",\n Self::Chat => \"chat\",\n Self::Anthropic => \"anthropic\",\n };\n f.write_str(value)\n }\n}\n\n/// How confidently a provider's text-only `stop` finish reason ends the user's\n/// action turn.\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]\npub enum ChatStopSemantics {\n /// A text-only `stop` is accepted as the provider's final answer.\n #[default]\n ReliableTerminal,\n /// A text-only `stop` needs a semantic completion decision before the turn\n /// can be considered finished.\n AmbiguousForActionTurns,\n}\n\nimpl<'de> Deserialize<'de> for WireApi {\n fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n where\n D: serde::Deserializer<'de>,\n {\n let value = String::deserialize(deserializer)?;\n match value.as_str() {\n \"responses\" => Ok(Self::Responses),\n \"chat\" => Ok(Self::Chat),\n \"anthropic\" | \"anthropic_messages\" | \"anthropic-messages\" => Ok(Self::Anthropic),\n _ => Err(serde::de::Error::unknown_variant(\n &value,\n &[\"responses\", \"chat\", \"anthropic\"],\n )),\n }\n }\n}\n\n/// Typed request and provider-tool budgets for a provider route.\n#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]\n#[schemars(deny_unknown_fields)]\npub struct ProviderRuntimePolicy {\n pub request_body_max_bytes: usize,\n pub retry_request_body_max_bytes: usize,\n pub web_search_max_uses: Option<u32>,\n}\n\nimpl Default for ProviderRuntimePolicy {\n fn default() -> Self {\n Self {\n request_body_max_bytes: DEFAULT_ANTHROPIC_REQUEST_BODY_MAX_BYTES,\n retry_request_body_max_bytes: DEFAULT_ANTHROPIC_RETRY_BODY_MAX_BYTES,\n web_search_max_uses: None,\n }\n }\n}\n\n/// Serializable representation of a provider definition.\n#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, JsonSchema)]\n#[schemars(deny_unknown_fields)]\npub struct ModelProviderInfo {\n /// Friendly display name.\n #[serde(default)]\n pub name: String,\n /// Base URL for the provider's OpenAI-compatible API.\n pub base_url: Option<String>,\n /// Environment variable that stores the user's API key for this provider.\n pub env_key: Option<String>,\n\n /// Optional instructions to help the user get a valid value for the\n /// variable and set it.\n pub env_key_instructions: Option<String>,\n /// Value to use with `Authorization: Bearer <token>` header. Use of this\n /// config is discouraged in favor of `env_key` for security reasons, but\n /// this may be necessary when using this programmatically.\n pub experimental_bearer_token: Option<String>,\n /// Command-backed bearer-token configuration for this provider.\n pub auth: Option<ModelProviderAuthInfo>,\n /// AWS SigV4 auth configuration for this provider.\n pub aws: Option<ModelProviderAwsAuthInfo>,\n /// Which wire protocol this provider expects.\n #[serde(default)]\n pub wire_api: WireApi,\n /// Optional query parameters to append to the base URL.\n pub query_params: Option<HashMap<String, String>>,\n /// Additional HTTP headers to include in requests to this provider where\n /// the (key, value) pairs are the header name and value.\n pub http_headers: Option<HashMap<String, String>>,\n /// Optional HTTP headers to include in requests to this provider where the\n /// (key, value) pairs are the header name and _environment variable_ whose\n /// value should be used. If the environment variable is not set, or the\n /// value is empty, the header will not be included in the request.\n pub env_http_headers: Option<HashMap<String, String>>,\n /// Optional `provider` object to include in Chat Completions request bodies.\n /// This is used by OpenRouter-compatible routes for provider routing\n /// preferences such as `order`, `sort`, `allow_fallbacks`, and\n /// `require_parameters`.\n pub chat_completions_provider: Option<Value>,\n /// Maximum number of times to retry a failed HTTP request to this provider.\n pub request_max_retries: Option<u64>,\n /// Number of times to retry reconnecting a dropped streaming response before failing.\n pub stream_max_retries: Option<u64>,\n /// Idle timeout (in milliseconds) to wait for activity on a streaming response before treating\n /// the connection as lost.\n pub stream_idle_timeout_ms: Option<u64>,\n /// Timeout (in milliseconds) to wait for actionable stream deltas after stream activity\n /// starts. Comment frames and empty data events do not reset this deadline.\n pub stream_actionable_timeout_ms: Option<u64>,\n /// Elapsed stream-attempt duration after which retry policy switches to the long-failure cap.\n pub stream_long_failure_retry_threshold_ms: Option<u64>,\n /// Maximum retry count for long stream failures. Values above one are clamped to one.\n pub stream_long_failure_max_retries: Option<u64>,\n /// Typed request and provider-tool budgets.\n #[serde(default)]\n pub runtime_policy: ProviderRuntimePolicy,\n /// Maximum time (in milliseconds) to wait for a websocket connection attempt before treating\n /// it as failed.\n pub websocket_connect_timeout_ms: Option<u64>,\n /// Does this provider require an OpenAI API Key or ChatGPT login token? If true,\n /// user is presented with login screen on first run, and login preference and token/key\n /// are stored in auth.json. If false (which is the default), login screen is skipped,\n /// and API key (if needed) comes from the \"env_key\" environment variable.\n #[serde(default)]\n pub requires_openai_auth: bool,\n /// Whether this provider supports the Responses API WebSocket transport.\n #[serde(default)]\n pub supports_websockets: bool,\n /// Whether this provider supports the standalone web-search endpoint.\n #[serde(default)]\n pub supports_standalone_web_search: bool,\n}\n\n/// AWS SigV4 auth configuration for a model provider.\n#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]\n#[schemars(deny_unknown_fields)]\npub struct ModelProviderAwsAuthInfo {\n /// AWS profile name to use. When unset, the AWS SDK default chain decides.\n pub profile: Option<String>,\n /// AWS region to use for provider-specific endpoints.\n pub region: Option<String>,\n}\n\nimpl ModelProviderInfo {\n pub fn validate(&self) -> std::result::Result<(), String> {\n if self.runtime_policy.request_body_max_bytes == 0 {\n return Err(\"runtime_policy.request_body_max_bytes must be greater than zero\".into());\n }\n if self.runtime_policy.retry_request_body_max_bytes == 0\n || self.runtime_policy.retry_request_body_max_bytes\n > self.runtime_policy.request_body_max_bytes\n {\n return Err(\n \"runtime_policy.retry_request_body_max_bytes must be nonzero and no greater than request_body_max_bytes\"\n .into(),\n );\n }\n if self.runtime_policy.web_search_max_uses == Some(0) {\n return Err(\"runtime_policy.web_search_max_uses must be greater than zero\".into());\n }\n if let Some(chat_completions_provider) = &self.chat_completions_provider\n && !chat_completions_provider.is_object()\n {\n return Err(\"chat_completions_provider must be a JSON object\".to_string());\n }\n\n if self.aws.is_some() {\n if self.supports_websockets {\n // TODO(celia-oai): Support AWS SigV4 signing for WebSocket\n // upgrade requests before allowing AWS-authenticated providers\n // to enable Responses-over-WebSocket.\n return Err(\"provider aws cannot be combined with supports_websockets\".to_string());\n }\n\n let mut conflicts = Vec::new();\n if self.env_key.is_some() {\n conflicts.push(\"env_key\");\n }\n if self.experimental_bearer_token.is_some() {\n conflicts.push(\"experimental_bearer_token\");\n }\n if self.auth.is_some() {\n conflicts.push(\"auth\");\n }\n if self.requires_openai_auth {\n conflicts.push(\"requires_openai_auth\");\n }\n\n if !conflicts.is_empty() {\n return Err(format!(\n \"provider aws cannot be combined with {}\",\n conflicts.join(\", \")\n ));\n }\n }\n\n let Some(auth) = self.auth.as_ref() else {\n return Ok(());\n };\n\n if auth.command.trim().is_empty() {\n return Err(\"provider auth.command must not be empty\".to_string());\n }\n\n let mut conflicts = Vec::new();\n if self.env_key.is_some() {\n conflicts.push(\"env_key\");\n }\n if self.experimental_bearer_token.is_some() {\n conflicts.push(\"experimental_bearer_token\");\n }\n if self.requires_openai_auth {\n conflicts.push(\"requires_openai_auth\");\n }\n\n if conflicts.is_empty() {\n Ok(())\n } else {\n Err(format!(\n \"provider auth cannot be combined with {}\",\n conflicts.join(\", \")\n ))\n }\n }\n\n fn build_header_map(&self) -> CodexResult<HeaderMap> {\n let capacity = self.http_headers.as_ref().map_or(0, HashMap::len)\n + self.env_http_headers.as_ref().map_or(0, HashMap::len);\n let mut headers = HeaderMap::with_capacity(capacity);\n if let Some(extra) = &self.http_headers {\n for (k, v) in extra {\n if let (Ok(name), Ok(value)) = (HeaderName::try_from(k), HeaderValue::try_from(v)) {\n headers.insert(name, value);\n }\n }\n }\n\n if let Some(env_headers) = &self.env_http_headers {\n for (header, env_var) in env_headers {\n if let Ok(val) = std::env::var(env_var)\n && !val.trim().is_empty()\n && let (Ok(name), Ok(value)) =\n (HeaderName::try_from(header), HeaderValue::try_from(val))\n {\n headers.insert(name, value);\n }\n }\n }\n\n Ok(headers)\n }\n\n pub fn to_api_provider(&self, auth_mode: Option<AuthMode>) -> CodexResult<ApiProvider> {\n let default_base_url = if matches!(\n auth_mode,\n Some(\n AuthMode::Chatgpt\n | AuthMode::ChatgptAuthTokens\n | AuthMode::Headers\n | AuthMode::AgentIdentity\n | AuthMode::PersonalAccessToken\n )\n ) {\n CHATGPT_CODEX_BASE_URL\n } else {\n \"https://api.openai.com/v1\"\n };\n let base_url = self\n .base_url\n .clone()\n .unwrap_or_else(|| default_base_url.to_string());\n\n let headers = self.build_header_map()?;\n let retry = ApiRetryConfig {\n max_attempts: self.request_max_retries(),\n base_delay: Duration::from_millis(200),\n retry_429: self.retries_transient_rate_limits(),\n retry_5xx: true,\n retry_transport: true,\n };\n\n Ok(ApiProvider {\n name: self.name.clone(),\n base_url,\n query_params: self.query_params.clone(),\n headers,\n retry,\n stream_idle_timeout: self.stream_idle_timeout(),\n })\n }\n\n /// If `env_key` is Some, returns the API key for this provider if present\n /// (and non-empty) in the environment. If `env_key` is required but\n /// cannot be found, returns an error.\n pub fn api_key(&self) -> CodexResult<Option<String>> {\n match &self.env_key {\n Some(env_key) => {\n let api_key = std::env::var(env_key)\n .ok()\n .filter(|v| !v.trim().is_empty())\n .ok_or_else(|| {\n CodexErr::EnvVar(EnvVarError {\n var: env_key.clone(),\n instructions: self.env_key_instructions.clone(),\n })\n })?;\n Ok(Some(api_key))\n }\n None => Ok(None),\n }\n }\n\n /// Effective maximum number of request retries for this provider.\n pub fn request_max_retries(&self) -> u64 {\n self.request_max_retries\n .unwrap_or(DEFAULT_REQUEST_MAX_RETRIES)\n .min(MAX_REQUEST_MAX_RETRIES)\n }\n\n /// Effective maximum number of stream reconnection attempts for this provider.\n pub fn stream_max_retries(&self) -> u64 {\n self.stream_max_retries\n .unwrap_or(DEFAULT_STREAM_MAX_RETRIES)\n .min(MAX_STREAM_MAX_RETRIES)\n }\n\n /// Effective idle timeout for streaming responses.\n pub fn stream_idle_timeout(&self) -> Duration {\n self.stream_idle_timeout_ms\n .map(Duration::from_millis)\n .unwrap_or(Duration::from_millis(DEFAULT_STREAM_IDLE_TIMEOUT_MS))\n }\n\n /// Effective actionable-silence timeout for streaming responses.\n pub fn stream_actionable_timeout(&self) -> Duration {\n self.stream_actionable_timeout_ms\n .map(Duration::from_millis)\n .unwrap_or(Duration::from_millis(DEFAULT_STREAM_ACTIONABLE_TIMEOUT_MS))\n }\n\n /// Effective threshold for treating retryable stream errors as long failures.\n pub fn stream_long_failure_retry_threshold(&self) -> Duration {\n self.stream_long_failure_retry_threshold_ms\n .map(Duration::from_millis)\n .unwrap_or(Duration::from_millis(\n DEFAULT_STREAM_LONG_FAILURE_RETRY_THRESHOLD_MS,\n ))\n }\n\n /// Effective retry cap for long stream failures. This intentionally never exceeds one.\n pub fn stream_long_failure_max_retries(&self) -> u64 {\n self.stream_long_failure_max_retries\n .unwrap_or(DEFAULT_STREAM_LONG_FAILURE_MAX_RETRIES)\n .min(DEFAULT_STREAM_LONG_FAILURE_MAX_RETRIES)\n }\n\n /// Effective timeout for websocket connect attempts.\n pub fn websocket_connect_timeout(&self) -> Duration {\n self.websocket_connect_timeout_ms\n .map(Duration::from_millis)\n .unwrap_or(Duration::from_millis(DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS))\n }\n\n pub fn create_openai_provider(base_url: Option<String>) -> ModelProviderInfo {\n // An overridden OpenAI-compatible endpoint is only guaranteed to implement the HTTP\n // Responses API. Retain first-party websocket prewarming for the default endpoint while\n // falling back conservatively to SSE for custom endpoints.\n let supports_websockets = base_url.is_none();\n ModelProviderInfo {\n name: OPENAI_PROVIDER_NAME.into(),\n base_url,\n env_key: None,\n env_key_instructions: None,\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Responses,\n query_params: None,\n http_headers: Some(\n [(\n \"version\".to_string(),\n OPENAI_CODEX_COMPAT_VERSION.to_string(),\n )]\n .into_iter()\n .collect(),\n ),\n env_http_headers: Some(\n [\n (\n \"OpenAI-Organization\".to_string(),\n \"OPENAI_ORGANIZATION\".to_string(),\n ),\n (\"OpenAI-Project\".to_string(), \"OPENAI_PROJECT\".to_string()),\n ]\n .into_iter()\n .collect(),\n ),\n // Use global defaults for retry/timeout unless overridden in config.toml.\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: true,\n supports_websockets,\n supports_standalone_web_search: true,\n }\n }\n\n pub fn create_anthropic_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: ANTHROPIC_PROVIDER_NAME.into(),\n base_url: Some(ANTHROPIC_BASE_URL.into()),\n env_key: Some(ANTHROPIC_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Anthropic,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_claude_plan_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: CLAUDE_PLAN_PROVIDER_NAME.into(),\n base_url: Some(ANTHROPIC_BASE_URL.into()),\n env_key: None,\n env_key_instructions: None,\n experimental_bearer_token: None,\n auth: Some(ModelProviderAuthInfo {\n command: \"pfterminal\".to_string(),\n args: vec![\"internal-claude-oauth-token\".to_string()],\n timeout_ms: provider_auth_timeout_ms(),\n refresh_interval_ms: 60_000,\n cwd: root_absolute_path(),\n }),\n aws: None,\n wire_api: WireApi::Anthropic,\n query_params: None,\n http_headers: Some(HashMap::from([(\n \"anthropic-beta\".to_string(),\n \"claude-code-20250219,oauth-2025-04-20\".to_string(),\n )])),\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_ambient_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: AMBIENT_PROVIDER_NAME.into(),\n base_url: Some(AMBIENT_BASE_URL.into()),\n env_key: Some(AMBIENT_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Chat,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_pfterminal_plan_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: PFTERMINAL_PLAN_PROVIDER_NAME.into(),\n base_url: Some(\n std::env::var(\"PFTERMINAL_PLAN_BASE_URL\")\n .unwrap_or_else(|_| PFTERMINAL_PLAN_DEFAULT_BASE_URL.to_string()),\n ),\n env_key: Some(PFTERMINAL_PLAN_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Chat,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_kimi_code_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: KIMI_CODE_PROVIDER_NAME.into(),\n base_url: Some(KIMI_CODE_BASE_URL.into()),\n env_key: Some(KIMI_CODE_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Chat,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_zai_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: ZAI_PROVIDER_NAME.into(),\n base_url: Some(ZAI_BASE_URL.into()),\n env_key: Some(ZAI_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Chat,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_zai_anthropic_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: ZAI_ANTHROPIC_PROVIDER_NAME.into(),\n base_url: Some(ZAI_ANTHROPIC_BASE_URL.into()),\n env_key: Some(ZAI_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Anthropic,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_openrouter_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: OPENROUTER_PROVIDER_NAME.into(),\n base_url: Some(OPENROUTER_BASE_URL.into()),\n env_key: Some(OPENROUTER_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Chat,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_deepseek_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: DEEPSEEK_PROVIDER_NAME.into(),\n base_url: Some(DEEPSEEK_BASE_URL.into()),\n env_key: Some(DEEPSEEK_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Responses,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_openrouter_anthropic_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: OPENROUTER_ANTHROPIC_PROVIDER_NAME.into(),\n base_url: Some(OPENROUTER_BASE_URL.into()),\n env_key: Some(OPENROUTER_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Anthropic,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_meta_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: META_PROVIDER_NAME.into(),\n base_url: Some(META_BASE_URL.into()),\n env_key: Some(META_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Responses,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_baseten_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: BASETEN_PROVIDER_NAME.into(),\n base_url: Some(BASETEN_BASE_URL.into()),\n env_key: Some(BASETEN_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Chat,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_baseten_anthropic_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: BASETEN_ANTHROPIC_PROVIDER_NAME.into(),\n base_url: Some(BASETEN_BASE_URL.into()),\n env_key: Some(BASETEN_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Anthropic,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_vercel_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: VERCEL_PROVIDER_NAME.into(),\n base_url: Some(VERCEL_BASE_URL.into()),\n env_key: Some(VERCEL_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Responses,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_vercel_anthropic_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: VERCEL_ANTHROPIC_PROVIDER_NAME.into(),\n base_url: Some(VERCEL_BASE_URL.into()),\n env_key: Some(VERCEL_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Anthropic,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_vercel_anthropic_fast_provider() -> ModelProviderInfo {\n ModelProviderInfo {\n name: VERCEL_ANTHROPIC_FAST_PROVIDER_NAME.into(),\n base_url: Some(VERCEL_BASE_URL.into()),\n env_key: Some(VERCEL_API_KEY_ENV_VAR.into()),\n env_key_instructions: Some(provider_api_key_vault_instructions()),\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api: WireApi::Anthropic,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn create_amazon_bedrock_provider(\n aws: Option<ModelProviderAwsAuthInfo>,\n ) -> ModelProviderInfo {\n ModelProviderInfo {\n name: AMAZON_BEDROCK_PROVIDER_NAME.into(),\n // The runtime provider derives the regional Mantle endpoint when\n // this is unset. A configured value is therefore unambiguously an\n // endpoint override.\n base_url: None,\n env_key: None,\n env_key_instructions: None,\n experimental_bearer_token: None,\n auth: None,\n aws: Some(aws.unwrap_or(ModelProviderAwsAuthInfo {\n profile: None,\n region: None,\n })),\n wire_api: WireApi::Responses,\n query_params: None,\n http_headers: Some(HashMap::from([(\n AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_HEADER.to_string(),\n AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_VALUE.to_string(),\n )])),\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n }\n\n pub fn is_openai(&self) -> bool {\n self.name == OPENAI_PROVIDER_NAME\n }\n\n pub fn is_anthropic(&self) -> bool {\n self.name == ANTHROPIC_PROVIDER_NAME\n }\n\n pub fn is_claude_plan(&self) -> bool {\n self.name == CLAUDE_PLAN_PROVIDER_NAME\n }\n\n /// Direct Anthropic API keys are sent as `x-api-key`; other built-in\n /// provider API keys remain Bearer tokens.\n pub fn api_key_header_name(&self) -> Option<&'static str> {\n (self.env_key.as_deref() == Some(ANTHROPIC_API_KEY_ENV_VAR)).then_some(\"x-api-key\")\n }\n\n pub fn is_ambient(&self) -> bool {\n self.name == AMBIENT_PROVIDER_NAME\n }\n\n pub fn is_pfterminal_plan(&self) -> bool {\n self.name == PFTERMINAL_PLAN_PROVIDER_NAME\n }\n\n pub fn is_kimi_code(&self) -> bool {\n self.name == KIMI_CODE_PROVIDER_NAME\n }\n\n pub fn chat_stop_semantics(&self) -> ChatStopSemantics {\n if self.is_kimi_code() {\n ChatStopSemantics::AmbiguousForActionTurns\n } else {\n ChatStopSemantics::ReliableTerminal\n }\n }\n\n pub fn is_zai(&self) -> bool {\n self.name == ZAI_PROVIDER_NAME\n }\n\n pub fn is_openrouter(&self) -> bool {\n self.name == OPENROUTER_PROVIDER_NAME\n }\n\n pub fn is_deepseek(&self) -> bool {\n self.name == DEEPSEEK_PROVIDER_NAME\n }\n\n pub fn is_meta(&self) -> bool {\n self.name == META_PROVIDER_NAME\n }\n\n pub fn is_baseten(&self) -> bool {\n self.name == BASETEN_PROVIDER_NAME\n }\n\n pub fn is_vercel(&self) -> bool {\n self.name == VERCEL_PROVIDER_NAME\n }\n\n /// Detect Vercel AI Gateway by endpoint so renamed custom providers retain\n /// the gateway's wire behavior without relying on display-name conventions.\n pub fn is_vercel_gateway(&self) -> bool {\n self.base_url\n .as_deref()\n .is_some_and(is_vercel_gateway_base_url)\n }\n\n pub fn uses_openai_actor_authorization(&self) -> bool {\n !self.requires_openai_auth\n && self.http_headers.as_ref().is_some_and(|headers| {\n headers.iter().any(|(name, value)| {\n name.eq_ignore_ascii_case(OPENAI_ACTOR_AUTHORIZATION_HEADER)\n && !value.trim().is_empty()\n })\n })\n }\n\n pub fn is_amazon_bedrock(&self) -> bool {\n self.name == AMAZON_BEDROCK_PROVIDER_NAME\n }\n\n fn retries_transient_rate_limits(&self) -> bool {\n self.is_zai()\n }\n\n pub fn supports_remote_compaction(&self) -> bool {\n self.is_openai() || is_azure_responses_provider(&self.name, self.base_url.as_deref())\n }\n\n pub fn has_command_auth(&self) -> bool {\n self.auth.is_some()\n }\n}\n\npub const DEFAULT_LMSTUDIO_PORT: u16 = 1234;\npub const DEFAULT_OLLAMA_PORT: u16 = 11434;\n\npub const LMSTUDIO_OSS_PROVIDER_ID: &str = \"lmstudio\";\npub const OLLAMA_OSS_PROVIDER_ID: &str = \"ollama\";\n\n/// Built-in default provider list.\npub fn built_in_model_providers(\n openai_base_url: Option<String>,\n) -> HashMap<String, ModelProviderInfo> {\n use ModelProviderInfo as P;\n let openai_provider = P::create_openai_provider(openai_base_url);\n let anthropic_provider = P::create_anthropic_provider();\n let claude_plan_provider = P::create_claude_plan_provider();\n let ambient_provider = P::create_ambient_provider();\n let pfterminal_plan_provider = P::create_pfterminal_plan_provider();\n let kimi_code_provider = P::create_kimi_code_provider();\n let zai_provider = P::create_zai_provider();\n let zai_anthropic_provider = P::create_zai_anthropic_provider();\n let openrouter_provider = P::create_openrouter_provider();\n let openrouter_anthropic_provider = P::create_openrouter_anthropic_provider();\n let deepseek_provider = P::create_deepseek_provider();\n let meta_provider = P::create_meta_provider();\n let baseten_provider = P::create_baseten_provider();\n let baseten_anthropic_provider = P::create_baseten_anthropic_provider();\n let vercel_provider = P::create_vercel_provider();\n let vercel_anthropic_provider = P::create_vercel_anthropic_provider();\n let vercel_anthropic_fast_provider = P::create_vercel_anthropic_fast_provider();\n let amazon_bedrock_provider = P::create_amazon_bedrock_provider(/*aws*/ None);\n\n // PFTerminal bundles the first-party OpenAI provider, the local OSS\n // providers, and the curated third-party coding providers exposed in the\n // login/model picker UX. Users can still add more providers in config.toml.\n [\n (ANTHROPIC_PROVIDER_ID, anthropic_provider),\n (CLAUDE_PLAN_PROVIDER_ID, claude_plan_provider),\n (AMBIENT_PROVIDER_ID, ambient_provider),\n (PFTERMINAL_PLAN_PROVIDER_ID, pfterminal_plan_provider),\n (KIMI_CODE_PROVIDER_ID, kimi_code_provider),\n (ZAI_PROVIDER_ID, zai_provider),\n (ZAI_ANTHROPIC_PROVIDER_ID, zai_anthropic_provider),\n (OPENROUTER_PROVIDER_ID, openrouter_provider),\n (\n OPENROUTER_ANTHROPIC_PROVIDER_ID,\n openrouter_anthropic_provider,\n ),\n (DEEPSEEK_PROVIDER_ID, deepseek_provider),\n (META_PROVIDER_ID, meta_provider),\n (BASETEN_PROVIDER_ID, baseten_provider),\n (BASETEN_ANTHROPIC_PROVIDER_ID, baseten_anthropic_provider),\n (VERCEL_PROVIDER_ID, vercel_provider),\n (VERCEL_ANTHROPIC_PROVIDER_ID, vercel_anthropic_provider),\n (\n VERCEL_ANTHROPIC_FAST_PROVIDER_ID,\n vercel_anthropic_fast_provider,\n ),\n (OPENAI_PROVIDER_ID, openai_provider),\n (AMAZON_BEDROCK_PROVIDER_ID, amazon_bedrock_provider),\n (\n OLLAMA_OSS_PROVIDER_ID,\n create_oss_provider(DEFAULT_OLLAMA_PORT, WireApi::Responses),\n ),\n (\n LMSTUDIO_OSS_PROVIDER_ID,\n create_oss_provider(DEFAULT_LMSTUDIO_PORT, WireApi::Responses),\n ),\n ]\n .into_iter()\n .map(|(k, v)| (k.to_string(), v))\n .collect()\n}\n\n/// Merge configured providers into the built-in provider catalog.\n///\n/// Configured providers extend the built-in set. Built-in providers are not\n/// generally overridable, but the built-in Amazon Bedrock provider allows the\n/// user to customize its endpoint, authentication, headers, and AWS settings.\npub fn merge_configured_model_providers(\n mut model_providers: HashMap<String, ModelProviderInfo>,\n configured_model_providers: HashMap<String, ModelProviderInfo>,\n) -> Result<HashMap<String, ModelProviderInfo>, String> {\n for (key, mut provider) in configured_model_providers {\n if key == AMAZON_BEDROCK_PROVIDER_ID {\n let base_url_override = provider.base_url.take();\n let auth_override = provider.auth.take();\n let aws_override = provider.aws.take();\n let http_headers_override = provider.http_headers.take();\n if provider != ModelProviderInfo::default() {\n return Err(format!(\n \"model_providers.{AMAZON_BEDROCK_PROVIDER_ID} only supports changing \\\n`base_url`, `auth`, `http_headers`, `aws.profile`, and `aws.region`; other non-default \\\nprovider fields are not supported\"\n ));\n }\n\n if let Some(built_in_provider) = model_providers.get_mut(AMAZON_BEDROCK_PROVIDER_ID) {\n built_in_provider.base_url = base_url_override;\n built_in_provider.auth = auth_override;\n if let Some(aws_override) = aws_override {\n built_in_provider.aws = Some(aws_override);\n }\n if let Some(http_headers_override) = http_headers_override {\n built_in_provider\n .http_headers\n .get_or_insert_default()\n .extend(http_headers_override);\n }\n }\n } else if let Some(built_in_provider) = model_providers.get_mut(&key) {\n apply_transport_overrides(built_in_provider, provider);\n } else {\n model_providers.insert(key, provider);\n }\n }\n\n Ok(model_providers)\n}\n\nfn apply_transport_overrides(\n built_in_provider: &mut ModelProviderInfo,\n configured_provider: ModelProviderInfo,\n) {\n if configured_provider.request_max_retries.is_some() {\n built_in_provider.request_max_retries = configured_provider.request_max_retries;\n }\n if configured_provider.stream_max_retries.is_some() {\n built_in_provider.stream_max_retries = configured_provider.stream_max_retries;\n }\n if configured_provider.stream_idle_timeout_ms.is_some() {\n built_in_provider.stream_idle_timeout_ms = configured_provider.stream_idle_timeout_ms;\n }\n if configured_provider.stream_actionable_timeout_ms.is_some() {\n built_in_provider.stream_actionable_timeout_ms =\n configured_provider.stream_actionable_timeout_ms;\n }\n if configured_provider\n .stream_long_failure_retry_threshold_ms\n .is_some()\n {\n built_in_provider.stream_long_failure_retry_threshold_ms =\n configured_provider.stream_long_failure_retry_threshold_ms;\n }\n if configured_provider\n .stream_long_failure_max_retries\n .is_some()\n {\n built_in_provider.stream_long_failure_max_retries =\n configured_provider.stream_long_failure_max_retries;\n }\n if configured_provider.websocket_connect_timeout_ms.is_some() {\n built_in_provider.websocket_connect_timeout_ms =\n configured_provider.websocket_connect_timeout_ms;\n }\n}\n\npub fn create_oss_provider(default_provider_port: u16, wire_api: WireApi) -> ModelProviderInfo {\n // These CODEX_OSS_ environment variables are experimental: we may\n // switch to reading values from config.toml instead.\n let default_codex_oss_base_url = format!(\n \"http://localhost:{codex_oss_port}/v1\",\n codex_oss_port = std::env::var(\"CODEX_OSS_PORT\")\n .ok()\n .filter(|value| !value.trim().is_empty())\n .and_then(|value| value.parse::<u16>().ok())\n .unwrap_or(default_provider_port)\n );\n\n let codex_oss_base_url = std::env::var(\"CODEX_OSS_BASE_URL\")\n .ok()\n .filter(|v| !v.trim().is_empty())\n .unwrap_or(default_codex_oss_base_url);\n create_oss_provider_with_base_url(&codex_oss_base_url, wire_api)\n}\n\npub fn create_oss_provider_with_base_url(base_url: &str, wire_api: WireApi) -> ModelProviderInfo {\n ModelProviderInfo {\n name: OSS_PROVIDER_NAME.into(),\n base_url: Some(base_url.into()),\n env_key: None,\n env_key_instructions: None,\n experimental_bearer_token: None,\n auth: None,\n aws: None,\n wire_api,\n query_params: None,\n http_headers: None,\n env_http_headers: None,\n chat_completions_provider: None,\n request_max_retries: None,\n stream_max_retries: None,\n stream_idle_timeout_ms: None,\n stream_actionable_timeout_ms: None,\n stream_long_failure_retry_threshold_ms: None,\n stream_long_failure_max_retries: None,\n runtime_policy: Default::default(),\n websocket_connect_timeout_ms: None,\n requires_openai_auth: false,\n supports_websockets: false,\n supports_standalone_web_search: false,\n }\n}\n\n#[cfg(test)]\n#[path = \"model_provider_info_tests.rs\"]\nmod tests;\n"}}}} |