connector::provider — registry and API keys
The provider registry: which backends exist, their base URLs, default models, and how API keys are resolved and cached. Local model servers are flagged and configured by URL instead of API key.
The registry
Each provider has a [ProviderConfig]:
pub struct ProviderConfig {
pub name: &'static str,
pub family: Family, // OpenAICompatible | Gemini | Claude
pub base_url: &'static str,
pub default_model: &'static str,
pub needs_extra_headers: bool, // e.g. OpenRouter
pub local: bool, // local model server (URL, not API key)
}
~28 providers are registered: openai, groq, mistral, together,
openrouter, xai, deepseek, perplexity, fireworks, cohere,
huggingface, sambanova, poe, cerebras, nvidia, anyscale,
vercel, cloudflare, azure, ollama, lmstudio, vllm, llamacpp,
gemini, claude, zai, charm, opencode. The local model servers (ollama,
lmstudio, vllm, llamacpp, and the extra ones below) point at
http://localhost:*.
Local providers (configured by URL, never by API key):
| Provider | Default port |
|---|---|
| ollama | 11434 |
| llamacpp | 8080 |
| lmstudio | 1234 |
| vllm | 8000 |
| llamafile | 8080 |
| koboldcpp | 5001 |
| text-generation-webui | 5000 |
| localai | 8080 |
| jan | 1337 |
| gpt4all | 4891 |
| aphrodite | 2242 |
| sglang | 30000 |
| tabbyapi | 5000 |
Enumerating providers
pub fn known_providers() -> impl Iterator<Item = &'static str>
pub fn known_providers_with_env() -> impl Iterator<Item = (&'static str, &'static str)>
pub fn get_provider_env_var(provider: &str) -> Option<&'static str>
pub fn is_local_provider(provider: &str) -> bool
pub fn known_local_providers() -> impl Iterator<Item = &'static str>
pub fn get_provider(name: &str) -> Option<&'static ProviderConfig>
known_providers_with_env pairs each provider with its key env var
(OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, …) — handy for UI
rendering. Note: local providers are excluded from known_providers_with_env
— they have no API key, and keeping them out protects --check-keyring and
detect_provider() from “detecting” a local server as a configured cloud
provider.
is_local_provider / known_local_providers classify the registry for the
ADD Provider screen: local providers prompt for a server URL instead of an API
key.
Normalizing local base URLs
pub fn normalize_local_base_url(provider: &str, url: &str) -> String
Users type http://127.0.0.1:8080 (no /v1). For OpenAI-compatible local
providers this appends /v1 internally; URLs that already have a path are
left untouched, and unknown providers are returned verbatim. The raw URL the
user typed is stored in setup.json — normalization is applied at request time,
never persisted, and lives in the TUI layer (not in with_base_url), so SDK
tests can keep mocking exact URLs.
The OpenCode Zen gateway (opencode) and its anonymous free tier
opencode points at https://opencode.ai/zen/v1 (default model
x-preview-f-free, key env OPENCODE_API_KEY). The gateway deliberately
serves an ANONYMOUS free tier: requests carrying the sentinel bearer
[ZEN_PUBLIC_KEY] ("public") are answered only for models flagged
allowAnonymous server-side and are rate-limited per IP. cosh’s rules for
using it — enforced in code, not convention:
- Account keys always win. A key resolved from keyring/env (or passed via
with_api_key) means the sentinel is never sent. - Strictly opt-in. The sentinel is only used when the connector flag
with_zen_public_tier(true)is set OR the process-wide switch [set_zen_public_tier_enabled] was turned on (the TUI flips it from the user’s persisted answer in setup.json). - Free models only. Anonymous requests are checked against
[
ZEN_FREE_MODELS] before any network activity; a paid model fails withConnectorError::AnonymousModelBlocked. Anonymouslist_modelsreturns only free models. The list deliberately excludesmuse-spark-1.2-contributor-free(its data policy grants training rights). - Honest identification. Zen requests carry
User-Agent: cosh/<version>([ZEN_USER_AGENT]) and no other client’s headers — anonymous traffic keeps the stricter public rate-limit bucket instead of imitating the official client.
pub const ZEN_PROVIDER: &str = "opencode";
pub const ZEN_PUBLIC_KEY: &str = "public";
pub const ZEN_USER_AGENT: &str = "cosh/<version>";
pub const ZEN_FREE_MODELS: &[&str];
pub fn is_zen_free_model(model: &str) -> bool;
pub fn set_zen_public_tier_enabled(enabled: bool); // process-wide opt-in
pub fn zen_public_tier_enabled() -> bool;
API key resolution
pub const COSH_SERVICE: &str = "cosh"; // default keyring service
pub fn get_api_key(provider: &str, service: Option<&str>) -> Option<String>
pub fn has_api_key(provider: &str) -> bool
pub fn detect_provider() -> Option<&'static str>
// Example: Check if a provider is configured
if has_api_key("openai") {
println!("OpenAI is configured");
}
// Example: Get the API key (respects priority: keyring → env → explicit)
if let Some(key) = get_api_key("anthropic", None) {
println!("Found Anthropic key");
}
// Example: Detect which provider the user is most likely using
if let Some(provider) = detect_provider() {
println!("Default provider: {}", provider);
}
Resolution order:
- OS keyring — the authoritative store for keys saved explicitly through
the app (
keyring::Entryundercoshby default). Preferring it over the environment means stale shell/.envexports can’t shadow a key the user configured.
Why keyring takes priority: Users who explicitly save keys through the app have made a deliberate configuration choice. Environment variables are often temporary (session exports,
.envfiles for development) and can become stale. By preferring keyring, we ensure the user’s intentional configuration isn’t accidentally overridden by forgotten environment state.
- Environment variable — per-provider fallback for providers never
stored (
get_api_key("openai", None)readsOPENAI_API_KEY).
Successful keyring lookups are cached in a process-lifetime map (keyed by
(service, env_var)) so repeated resolutions don’t re-hit the OS credential
store; misses are never cached, so keys added externally appear immediately.
Cached values are Zeroizing — wiped from memory on invalidation.
pub fn invalidate_api_key(env_var: &str) // forget one provider's cached key
pub fn clear_api_key_cache() // forget everything
Call invalidate_api_key after storing or updating a key, or the cache
serves the stale value.
has_api_key(provider)— “configured?” regardless of backend (keyring or env).detect_provider()— the first provider (in registration order) that has a key; the natural default for “which provider is this user using?”
Next: discovery — context windows and reasoning.
Summary
- The provider registry contains ~26 providers with
ProviderConfigentries: name, family (OpenAICompatible/Gemini/Claude), base URL, default model, extra-headers flag, andlocalflag. - Providers include cloud services (openai, groq, mistral, together, openrouter, xai, deepseek, perplexity, etc.) and local model servers (ollama, lmstudio, vllm, llamacpp, llamafile, koboldcpp, text-generation-webui, localai, jan, gpt4all, aphrodite, sglang, tabbyapi) pointing at localhost.
- Enumeration:
known_providers()(iterator),known_providers_with_env()(pairs with env vars), andget_provider_env_var()(env var name for a provider). Local providers are excluded fromknown_providers_with_env— they have no key and must not be “detected” as configured cloud providers. is_local_provider/known_local_providersclassify the registry;get_providerreturns the fullProviderConfig(default port, family, model).normalize_local_base_url(provider, url)appends/v1for bare OpenAI-compatible URLs; it is applied at request time in the TUI layer, never persisted.- API key resolution: OS keyring (authoritative store under
coshservice) → environment variable (fallback) → explicitwith_api_key(highest priority). - Keyring lookups are cached per process lifetime (keyed by service + env var) and
Zeroizing; misses are never cached so externally-added keys appear immediately. - Cache invalidation:
invalidate_api_key(env_var)after storing/updating a key, orclear_api_key_cache()to forget everything. has_api_key(provider)answers “configured?” regardless of backend;detect_provider()returns the first provider with a key (natural default).