feat(config): catalog authority — descriptors not compiled model lists (#5783)

* feat(config): Codewhale catalog authority; delete compiled model rosters

Compatible hosts (Baseten, Groq, Cerebras, SenseNova, Command Code) are
descriptors: wire, URL, env, live GET /v1/models. Offerings come from
catalog layers; a signed CWC fetch wins over models.dev. Command Code is
a row, not a ProviderKind. Do not import the Command Code CLI login.

Signed-off-by: CodeWhale Bot <[email protected]>

* fix(tui): cover Codewhale catalog sources in provenance matches

CodewhaleBundled/CodewhaleLive are first-party catalog authority, not
user override. rustfmt the descriptor module that failed lint.

Signed-off-by: CodeWhale Bot <[email protected]>

* fix(tui): keep catalog template docs visible after Command Code

The extra descriptor row filled the 16-row list budget and clipped
OpenCode Zen's docs URL. Grow the template list and assert the
descriptor endpoint plus Command Code.

Signed-off-by: CodeWhale Bot <[email protected]>

---------

Signed-off-by: CodeWhale Bot <[email protected]>
Co-authored-by: CodeWhale Bot <[email protected]>
This commit is contained in:
Hunter BownandCodeWhale Bot authored and GitHub committed 2026-09-01 01:08:37 +00:00
1 parent 3be41346d3
commit ceb30eba60
12 files changed
+366 -59

No files matched your search

+7
View File
@@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Public roster language is Pod. `/pod` is the customer surface; fleet remains
the internal wire, storage, and migration name (#5776).
- Provider catalogs: compatible hosts (Baseten, Groq, Cerebras, SenseNova,
Command Code) no longer compile a frozen model roster. Descriptors name the
wire, URL, and env; live `GET /v1/models` and a Codewhale-owned catalog
layer are the offering list. Command Code is a catalog/descriptor row, not
a `ProviderKind`. Catalog presence is not an availability, entitlement, or
provider-acceptance claim (#5783).
- Provider selection no longer probes or adopts an external CLI credential on
ordinary picker use. Reuse requires an explicit "Use external CLI credentials"
choice, exact-path confirmation, and Codewhale-owned revoke (#5772).
@@ -0,0 +1,63 @@
{
"schema": "codewhale.provider_descriptors.v1",
"note": "How to talk to a host. Model ids are not compiled here; live /v1/models and the Codewhale catalog are truth. default_model is a bootstrap hint only.",
"descriptors": [
{
"id": "sensenova",
"label": "SenseNova",
"wire": "openai-compatible",
"base_url": "https://token.sensenova.cn/v1",
"api_key_env": "SENSENOVA_API_KEY",
"default_model": "deepseek-v4-flash",
"discovery": "models_endpoint",
"aliases": ["sense-nova", "meituan-sensenova", "meituan-sensenova-cn"]
},
{
"id": "baseten",
"label": "Baseten",
"wire": "openai-compatible",
"base_url": "https://inference.baseten.co/v1",
"api_key_env": "BASETEN_API_KEY",
"default_model": "deepseek-ai/DeepSeek-V4-Pro",
"discovery": "models_endpoint",
"docs_url": "https://docs.baseten.co/inference/model-apis/overview",
"credential_url": "https://app.baseten.co/settings/api_keys",
"aliases": ["base-ten", "base_ten"]
},
{
"id": "groq",
"label": "Groq",
"wire": "openai-compatible",
"base_url": "https://api.groq.com/openai/v1",
"api_key_env": "GROQ_API_KEY",
"default_model": "llama-3.3-70b-versatile",
"discovery": "models_endpoint",
"docs_url": "https://console.groq.com/docs/quickstart",
"credential_url": "https://console.groq.com/keys"
},
{
"id": "cerebras",
"label": "Cerebras",
"wire": "openai-compatible",
"base_url": "https://api.cerebras.ai/v1",
"api_key_env": "CEREBRAS_API_KEY",
"default_model": "llama-3.3-70b",
"discovery": "models_endpoint",
"docs_url": "https://inference-docs.cerebras.ai/quickstart",
"credential_url": "https://cloud.cerebras.ai"
},
{
"id": "command-code",
"label": "Command Code",
"wire": "openai-compatible",
"base_url": "https://api.commandcode.ai/provider/v1",
"api_key_env": "COMMAND_CODE_API_KEY",
"default_model": "deepseek/deepseek-v4-flash",
"discovery": "models_endpoint",
"docs_url": "https://commandcode.ai/docs/provider",
"credential_url": "https://commandcode.ai/provider",
"guidance": "Published Provider API (OpenAI Chat Completions + GET /v1/models). Do not import the Command Code CLI login. Store COMMAND_CODE_API_KEY, not a raw key.",
"aliases": ["commandcode", "cmd-code"]
}
]
}
+37 -10
View File
@@ -6,12 +6,15 @@
//! refresh) and live [`ProviderCatalogDelta`]s; the HTTP `/models` fetch layer
//! lives above this module. Nothing here performs I/O or reads credentials.
//!
//! Layering (lowest precedence first; #4188):
//! Layering (lowest precedence first):
//!
//! ```text
//! bundled Models.dev snapshot (offline/stale fallback only — not competing truth)
//! < live Models.dev / provider `/models` cache
//! < user / custom overrides (custom endpoints, pinned models, explicit facts)
//! bundled Models.dev snapshot (legacy seed, not competing truth)
//! < bundled Codewhale catalog (Codewhale-owned offline snapshot)
//! < live Models.dev
//! < live provider `/v1/models` (credential-scoped workspace list)
//! < live Codewhale signed catalog (authority when present)
//! < config.toml / user overrides
//! ```
//!
//! After #4187, live Models.dev rows are preferred whenever present. The bundled
@@ -61,6 +64,10 @@ pub enum CatalogSource {
ModelsDevLive { fetched_at: u64 },
/// `config.toml` `[providers.*]` override (layer 30).
ConfigOverride,
/// Codewhale-owned bundled catalog snapshot (offline authority seed).
CodewhaleBundled { revision: String },
/// Live Codewhale signed catalog fetched from CWC / `CODEWHALE_CATALOG_URL`.
CodewhaleLive { revision: String, fetched_at: u64 },
}
/// One catalog-layer offering row.
@@ -625,12 +632,14 @@ impl CatalogSnapshot {
/// after every layer and is never overridden:
///
/// ```text
/// 0 bundled committed models.dev-shaped snapshot
/// 10 live models.dev models.dev refresh
/// 20 provider per-provider /v1/models refresh
/// 30 config config.toml [providers.*] overrides
/// 40 user user approved set (Phase 3 hook; empty in Phase 1)
/// policy DENY last, never overridden
/// 0 bundled committed models.dev-shaped snapshot
/// 5 codewhale bundled Codewhale-owned offline snapshot
/// 10 live models.dev models.dev refresh
/// 20 provider per-provider /v1/models refresh
/// 25 codewhale live signed CWC catalog (authority)
/// 30 config config.toml [providers.*] overrides
/// 40 user user approved set
/// policy DENY last, never overridden
/// ```
///
/// [`Self::with_live`] remains the combined live bucket so existing callers
@@ -639,9 +648,11 @@ impl CatalogSnapshot {
#[derive(Debug, Clone, Default)]
pub struct CatalogCompiler {
bundled: Vec<CatalogOffering>,
codewhale_bundled: Vec<CatalogOffering>,
models_dev_live: Vec<CatalogOffering>,
live: Vec<CatalogOffering>,
provider_live: Vec<CatalogOffering>,
codewhale_live: Vec<CatalogOffering>,
config: Vec<CatalogOffering>,
overrides: Vec<CatalogOffering>,
policy: crate::route::CatalogPolicy,
@@ -669,6 +680,20 @@ impl CatalogCompiler {
self
}
/// Add Codewhale-owned bundled catalog rows (layer 5).
#[must_use]
pub fn with_codewhale_bundled(mut self, rows: Vec<CatalogOffering>) -> Self {
self.codewhale_bundled.extend(rows);
self
}
/// Add live signed Codewhale catalog rows (layer 25; authority when present).
#[must_use]
pub fn with_codewhale_live(mut self, rows: Vec<CatalogOffering>) -> Self {
self.codewhale_live.extend(rows);
self
}
/// Add live models.dev refresh rows (layer 10).
#[must_use]
pub fn with_models_dev_live(mut self, rows: Vec<CatalogOffering>) -> Self {
@@ -722,9 +747,11 @@ impl CatalogCompiler {
for row in self
.bundled
.into_iter()
.chain(self.codewhale_bundled)
.chain(self.models_dev_live)
.chain(self.live)
.chain(self.provider_live)
.chain(self.codewhale_live)
.chain(self.config)
.chain(self.overrides)
{
+44
View File
@@ -1002,3 +1002,47 @@ fn live_offerings_normalize_models_dev_provider_aliases() {
assert!(rows.iter().all(|r| r.provider != "togetherai"));
assert!(rows.iter().all(|r| r.provider != "zhipuai"));
}
fn offering(provider: &str, wire: &str, source: CatalogSource) -> CatalogOffering {
CatalogOffering {
provider: provider.to_string(),
wire_model_id: wire.to_string(),
endpoint_key: "chat".to_string(),
source,
..CatalogOffering::default()
}
}
#[test]
fn codewhale_live_catalog_beats_models_dev_on_the_same_wire() {
let snapshot = CatalogCompiler::new()
.with_bundled(vec![offering(
"command-code",
"deepseek/deepseek-v4-flash",
CatalogSource::Bundled,
)])
.with_models_dev_live(vec![offering(
"command-code",
"deepseek/deepseek-v4-flash",
CatalogSource::ModelsDevLive { fetched_at: 1 },
)])
.with_codewhale_live(vec![offering(
"command-code",
"deepseek/deepseek-v4-flash",
CatalogSource::CodewhaleLive {
revision: "2026-08-31.1".into(),
fetched_at: 2,
},
)])
.compile();
let row = find(
&snapshot.offerings,
"command-code",
"deepseek/deepseek-v4-flash",
);
assert!(matches!(
row.source,
CatalogSource::CodewhaleLive { ref revision, fetched_at: 2 }
if revision == "2026-08-31.1"
));
}
+132
View File
@@ -0,0 +1,132 @@
//! OMP-style provider descriptors: how to talk to a host.
//!
//! Model ids are **not** compiled here. A descriptor names the wire, URL, env
//! var, and whether authenticated `GET /v1/models` is the catalog authority
//! for that host. Offerings come from the Codewhale catalog layers and live
//! provider `/models` refreshes.
use std::sync::OnceLock;
use serde::Deserialize;
const DESCRIPTORS_JSON: &str = include_str!("../assets/provider_descriptors.json");
/// How this host's model list is discovered.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DescriptorDiscovery {
/// Authenticated `GET {base_url}/models` is authoritative for this credential.
ModelsEndpoint,
/// No live discovery; only catalog/config rows.
None,
}
/// Transport used to send turns. Not a brand enum.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum DescriptorWire {
OpenaiCompatible,
AnthropicMessages,
}
#[derive(Debug, Deserialize)]
struct DescriptorFile {
descriptors: Vec<ProviderDescriptor>,
}
/// Data row describing a hosted OpenAI-compatible (or Anthropic Messages) gateway.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ProviderDescriptor {
pub id: String,
pub label: String,
pub wire: DescriptorWire,
pub base_url: String,
pub api_key_env: String,
pub default_model: String,
pub discovery: DescriptorDiscovery,
#[serde(default)]
pub docs_url: Option<String>,
#[serde(default)]
pub credential_url: Option<String>,
#[serde(default)]
pub guidance: Option<String>,
#[serde(default)]
pub aliases: Vec<String>,
}
impl ProviderDescriptor {
#[must_use]
pub fn matches(&self, needle: &str) -> bool {
let needle = needle.trim().to_ascii_lowercase().replace('_', "-");
if needle.is_empty() {
return false;
}
self.id == needle
|| self
.aliases
.iter()
.any(|alias| alias.eq_ignore_ascii_case(&needle))
}
}
static DESCRIPTORS: OnceLock<Vec<ProviderDescriptor>> = OnceLock::new();
/// Bundled compatible-host descriptors. Panics only if the committed JSON is invalid.
#[must_use]
pub fn bundled_provider_descriptors() -> &'static [ProviderDescriptor] {
DESCRIPTORS
.get_or_init(|| {
let file: DescriptorFile = serde_json::from_str(DESCRIPTORS_JSON)
.expect("committed provider_descriptors.json must parse");
file.descriptors
})
.as_slice()
}
#[must_use]
pub fn provider_descriptor(id: &str) -> Option<&'static ProviderDescriptor> {
bundled_provider_descriptors()
.iter()
.find(|descriptor| descriptor.matches(id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn descriptors_parse_and_command_code_is_a_row_not_a_kind() {
let rows = bundled_provider_descriptors();
assert!(
rows.len() >= 5,
"expected compatible hosts plus command-code"
);
for row in rows {
assert!(row.base_url.starts_with("https://"), "{}", row.id);
assert!(!row.api_key_env.is_empty(), "{}", row.id);
assert!(!row.default_model.is_empty(), "{}", row.id);
assert_eq!(row.discovery, DescriptorDiscovery::ModelsEndpoint);
assert_eq!(row.wire, DescriptorWire::OpenaiCompatible);
}
let cmd = provider_descriptor("command-code").expect("command-code");
assert_eq!(cmd.base_url, "https://api.commandcode.ai/provider/v1");
assert_eq!(cmd.api_key_env, "COMMAND_CODE_API_KEY");
assert_eq!(
provider_descriptor("cmd-code").map(|row| row.id.as_str()),
Some("command-code")
);
}
#[test]
fn descriptors_do_not_embed_model_rosters() {
let raw = DESCRIPTORS_JSON;
assert!(
!raw.contains("moonshotai/Kimi-K2.7-Code"),
"do not compile a Baseten/Kimi roster into descriptors"
);
assert!(
!raw.contains("openai/gpt-oss-120b"),
"do not compile a Groq roster into descriptors"
);
}
}
+6 -6
View File
@@ -3,6 +3,7 @@ pub mod auth_source;
pub mod auto_model;
pub mod catalog;
mod config_document;
pub mod descriptors;
pub mod device_code;
pub mod external_credentials;
mod harness;
@@ -31,12 +32,11 @@ pub(crate) use provider_defaults::*;
pub use provider_kind::ProviderKind;
pub use provider_templates::{
AGNES_TEMPLATE_ID, BASETEN_API_KEY_ENV, BASETEN_BASE_URL, BASETEN_DEFAULT_MODEL,
BASETEN_MODELS, BASETEN_TEMPLATE_ID, CEREBRAS_API_KEY_ENV, CEREBRAS_BASE_URL,
CEREBRAS_DEFAULT_MODEL, CEREBRAS_MODELS, CEREBRAS_TEMPLATE_ID, GROQ_API_KEY_ENV, GROQ_BASE_URL,
GROQ_DEFAULT_MODEL, GROQ_MODELS, GROQ_TEMPLATE_ID, ProviderSetupApply, ProviderSetupTemplate,
SENSENOVA_API_KEY_ENV, SENSENOVA_BASE_URL, SENSENOVA_DEFAULT_MODEL, SENSENOVA_MODELS,
SENSENOVA_TEMPLATE_ID, compatible_provider_setup_templates, provider_setup_template,
provider_setup_templates,
BASETEN_TEMPLATE_ID, CEREBRAS_API_KEY_ENV, CEREBRAS_BASE_URL, CEREBRAS_DEFAULT_MODEL,
CEREBRAS_TEMPLATE_ID, COMMAND_CODE_TEMPLATE_ID, GROQ_API_KEY_ENV, GROQ_BASE_URL,
GROQ_DEFAULT_MODEL, GROQ_TEMPLATE_ID, ProviderSetupApply, ProviderSetupTemplate,
SENSENOVA_API_KEY_ENV, SENSENOVA_BASE_URL, SENSENOVA_DEFAULT_MODEL, SENSENOVA_TEMPLATE_ID,
compatible_provider_setup_templates, provider_setup_template, provider_setup_templates,
};
pub use setup_state::{
ConstitutionAuthoring, ConstitutionChoice, ConstitutionSource, ConstitutionValidity,
+20 -8
View File
@@ -53,6 +53,8 @@ pub enum PricingProvenance {
UserOverride,
/// No sourced price.
Unknown,
/// From the Codewhale-owned signed/bundled catalog.
CodewhaleCatalog,
}
impl PricingProvenance {
@@ -65,6 +67,7 @@ impl PricingProvenance {
Self::ProviderDocs => "provider_docs",
Self::UserOverride => "user_override",
Self::Unknown => "unknown",
Self::CodewhaleCatalog => "codewhale_catalog",
}
}
@@ -79,7 +82,10 @@ impl PricingProvenance {
pub fn is_authoritative_without_freshness_check(&self) -> bool {
matches!(
self,
Self::ModelsDevBundled | Self::ProviderDocs | Self::UserOverride
Self::ModelsDevBundled
| Self::ProviderDocs
| Self::UserOverride
| Self::CodewhaleCatalog
)
}
}
@@ -505,17 +511,21 @@ fn provenance_from_source(source: &CatalogSource) -> PricingProvenance {
CatalogSource::ConfigOverride | CatalogSource::UserOverride => {
PricingProvenance::UserOverride
}
CatalogSource::CodewhaleBundled { .. } | CatalogSource::CodewhaleLive { .. } => {
PricingProvenance::CodewhaleCatalog
}
}
}
fn effective_at_from_source(source: &CatalogSource) -> Option<u64> {
match source {
CatalogSource::Live { fetched_at, .. } | CatalogSource::ModelsDevLive { fetched_at } => {
Some(*fetched_at)
}
CatalogSource::Bundled | CatalogSource::ConfigOverride | CatalogSource::UserOverride => {
None
}
CatalogSource::Live { fetched_at, .. }
| CatalogSource::ModelsDevLive { fetched_at }
| CatalogSource::CodewhaleLive { fetched_at, .. } => Some(*fetched_at),
CatalogSource::Bundled
| CatalogSource::ConfigOverride
| CatalogSource::UserOverride
| CatalogSource::CodewhaleBundled { .. } => None,
}
}
@@ -528,7 +538,9 @@ fn endpoint_fingerprint_from_source(source: &CatalogSource) -> Option<String> {
CatalogSource::Bundled
| CatalogSource::ModelsDevLive { .. }
| CatalogSource::ConfigOverride
| CatalogSource::UserOverride => None,
| CatalogSource::UserOverride
| CatalogSource::CodewhaleBundled { .. }
| CatalogSource::CodewhaleLive { .. } => None,
}
}
+28 -28
View File
@@ -6,8 +6,10 @@
//! - first-class gateways users still treat as "paste a Base URL"
//! (OpenCode Zen / Go), and
//! - named OpenAI-compatible custom routes that are not `ProviderKind`
//! variants (SenseNova, Baseten, Groq, Cerebras). Hosted Chat Completions
//! backends are data rows here — not new enum variants. Distinct *wires*
//! variants (SenseNova, Baseten, Groq, Cerebras, Command Code). Hosted
//! Chat Completions backends are descriptor rows — not new enum variants
//! and not compiled model rosters. Live `GET /v1/models` and the
//! Codewhale catalog are the offering list. Distinct *wires*
//! (Anthropic Messages, Codex Responses, Google thought signatures) stay
//! on `ProviderKind`.
//!
@@ -28,7 +30,6 @@ pub const SENSENOVA_TEMPLATE_ID: &str = "sensenova";
pub const SENSENOVA_BASE_URL: &str = "https://token.sensenova.cn/v1";
pub const SENSENOVA_DEFAULT_MODEL: &str = "deepseek-v4-flash";
pub const SENSENOVA_API_KEY_ENV: &str = "SENSENOVA_API_KEY";
pub const SENSENOVA_MODELS: &[&str] = &[SENSENOVA_DEFAULT_MODEL];
/// Agnes is requested by #5350 but has no published OpenAI-compatible
/// host in this repository.
@@ -39,28 +40,19 @@ pub const BASETEN_TEMPLATE_ID: &str = "baseten";
pub const BASETEN_BASE_URL: &str = "https://inference.baseten.co/v1";
pub const BASETEN_DEFAULT_MODEL: &str = "deepseek-ai/DeepSeek-V4-Pro";
pub const BASETEN_API_KEY_ENV: &str = "BASETEN_API_KEY";
pub const BASETEN_MODELS: &[&str] = &[
BASETEN_DEFAULT_MODEL,
"deepseek-ai/DeepSeek-V4-Flash-0731",
"deepseek-ai/DeepSeek-V4-Pro-0813",
"zai-org/GLM-5.2",
"zai-org/GLM-5.3-Flash",
"moonshotai/Kimi-K2.7-Code",
];
/// Groq — OpenAI Chat Completions hosted inference.
pub const GROQ_TEMPLATE_ID: &str = "groq";
pub const GROQ_BASE_URL: &str = "https://api.groq.com/openai/v1";
pub const GROQ_DEFAULT_MODEL: &str = "llama-3.3-70b-versatile";
pub const GROQ_API_KEY_ENV: &str = "GROQ_API_KEY";
pub const GROQ_MODELS: &[&str] = &[GROQ_DEFAULT_MODEL, "openai/gpt-oss-120b"];
/// Cerebras — OpenAI Chat Completions hosted inference.
pub const CEREBRAS_TEMPLATE_ID: &str = "cerebras";
pub const CEREBRAS_BASE_URL: &str = "https://api.cerebras.ai/v1";
pub const CEREBRAS_DEFAULT_MODEL: &str = "llama-3.3-70b";
pub const CEREBRAS_API_KEY_ENV: &str = "CEREBRAS_API_KEY";
pub const CEREBRAS_MODELS: &[&str] = &[CEREBRAS_DEFAULT_MODEL];
pub const COMMAND_CODE_TEMPLATE_ID: &str = "command-code";
/// How a beginner template is applied.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -86,7 +78,6 @@ pub struct ProviderSetupTemplate {
pub apply: ProviderSetupApply,
base_url: Option<&'static str>,
default_model: Option<&'static str>,
models: &'static [&'static str],
api_key_env: Option<&'static str>,
docs_url: Option<&'static str>,
credential_url: Option<&'static str>,
@@ -116,9 +107,8 @@ impl ProviderSetupTemplate {
}
}
/// Models shown in setup / `/model` when the live catalog is empty or
/// Models.dev refresh failed. First-class Zen/Go use the curated
/// roster already owned by the route catalog.
/// Bootstrap model ids when the live catalog has not yet answered.
/// Compatible hosts expose only the descriptor default — never a compiled roster.
#[must_use]
pub fn picker_models(self) -> Vec<&'static str> {
match self.apply {
@@ -128,11 +118,10 @@ impl ProviderSetupTemplate {
ProviderSetupApply::FirstClass(ProviderKind::OpencodeGo) => {
OPENCODE_GO_CHAT_MODELS.to_vec()
}
ProviderSetupApply::FirstClass(_) => self
ProviderSetupApply::FirstClass(_) | ProviderSetupApply::Compatible => self
.default_model()
.map(|model| vec![model])
.unwrap_or_default(),
ProviderSetupApply::Compatible => self.models.to_vec(),
ProviderSetupApply::Unpublished => Vec::new(),
}
}
@@ -224,7 +213,6 @@ const TEMPLATES: &[ProviderSetupTemplate] = &[
apply: ProviderSetupApply::FirstClass(ProviderKind::OpencodeZen),
base_url: None,
default_model: None,
models: &[],
api_key_env: None,
docs_url: None,
credential_url: None,
@@ -236,7 +224,6 @@ const TEMPLATES: &[ProviderSetupTemplate] = &[
apply: ProviderSetupApply::FirstClass(ProviderKind::OpencodeGo),
base_url: None,
default_model: None,
models: &[],
api_key_env: None,
docs_url: None,
credential_url: None,
@@ -248,7 +235,6 @@ const TEMPLATES: &[ProviderSetupTemplate] = &[
apply: ProviderSetupApply::Compatible,
base_url: Some(SENSENOVA_BASE_URL),
default_model: Some(SENSENOVA_DEFAULT_MODEL),
models: SENSENOVA_MODELS,
api_key_env: Some(SENSENOVA_API_KEY_ENV),
docs_url: None,
credential_url: None,
@@ -260,7 +246,6 @@ const TEMPLATES: &[ProviderSetupTemplate] = &[
apply: ProviderSetupApply::Compatible,
base_url: Some(BASETEN_BASE_URL),
default_model: Some(BASETEN_DEFAULT_MODEL),
models: BASETEN_MODELS,
api_key_env: Some(BASETEN_API_KEY_ENV),
docs_url: Some("https://docs.baseten.co/inference/model-apis/overview"),
credential_url: Some("https://app.baseten.co/settings/api_keys"),
@@ -272,7 +257,6 @@ const TEMPLATES: &[ProviderSetupTemplate] = &[
apply: ProviderSetupApply::Compatible,
base_url: Some(GROQ_BASE_URL),
default_model: Some(GROQ_DEFAULT_MODEL),
models: GROQ_MODELS,
api_key_env: Some(GROQ_API_KEY_ENV),
docs_url: Some("https://console.groq.com/docs/quickstart"),
credential_url: Some("https://console.groq.com/keys"),
@@ -284,19 +268,28 @@ const TEMPLATES: &[ProviderSetupTemplate] = &[
apply: ProviderSetupApply::Compatible,
base_url: Some(CEREBRAS_BASE_URL),
default_model: Some(CEREBRAS_DEFAULT_MODEL),
models: CEREBRAS_MODELS,
api_key_env: Some(CEREBRAS_API_KEY_ENV),
docs_url: Some("https://inference-docs.cerebras.ai/quickstart"),
credential_url: Some("https://cloud.cerebras.ai"),
guidance: "Cerebras hosted inference. OpenAI Chat Completions. Store CEREBRAS_API_KEY, not a raw key.",
},
ProviderSetupTemplate {
id: COMMAND_CODE_TEMPLATE_ID,
display_name: "Command Code",
apply: ProviderSetupApply::Compatible,
base_url: Some("https://api.commandcode.ai/provider/v1"),
default_model: Some("deepseek/deepseek-v4-flash"),
api_key_env: Some("COMMAND_CODE_API_KEY"),
docs_url: Some("https://commandcode.ai/docs/provider"),
credential_url: Some("https://commandcode.ai/provider"),
guidance: "Published Provider API. Live GET /v1/models is the roster. Do not import the Command Code CLI login. Store COMMAND_CODE_API_KEY, not a raw key.",
},
ProviderSetupTemplate {
id: AGNES_TEMPLATE_ID,
display_name: "Agnes",
apply: ProviderSetupApply::Unpublished,
base_url: None,
default_model: None,
models: &[],
api_key_env: None,
docs_url: None,
credential_url: None,
@@ -334,6 +327,7 @@ pub fn provider_setup_template(id: &str) -> Option<&'static ProviderSetupTemplat
template.id == SENSENOVA_TEMPLATE_ID
}
"base-ten" | "base_ten" => template.id == BASETEN_TEMPLATE_ID,
"commandcode" | "cmd-code" => template.id == COMMAND_CODE_TEMPLATE_ID,
_ => false,
})
})
@@ -434,7 +428,7 @@ mod tests {
assert_eq!(sense.base_url(), Some(SENSENOVA_BASE_URL));
assert_eq!(sense.default_model(), Some(SENSENOVA_DEFAULT_MODEL));
assert_eq!(sense.api_key_env(), Some(SENSENOVA_API_KEY_ENV));
assert_eq!(sense.picker_models(), SENSENOVA_MODELS);
assert_eq!(sense.picker_models(), vec![SENSENOVA_DEFAULT_MODEL]);
assert!(sense.docs_url().is_none());
assert!(sense.credential_url().is_none());
}
@@ -455,7 +449,7 @@ mod tests {
fn settings_value_names_fillable_then_unpublished() {
assert_eq!(
ProviderSetupTemplate::settings_value(),
"opencode-zen, opencode-go, sensenova, baseten, groq, cerebras; agnes unpublished"
"opencode-zen, opencode-go, sensenova, baseten, groq, cerebras, command-code; agnes unpublished"
);
}
@@ -493,6 +487,12 @@ mod tests {
CEREBRAS_BASE_URL,
CEREBRAS_API_KEY_ENV,
),
(
"command-code",
COMMAND_CODE_TEMPLATE_ID,
"https://api.commandcode.ai/provider/v1",
"COMMAND_CODE_API_KEY",
),
] {
let template = provider_setup_template(alias).unwrap_or_else(|| panic!("{alias}"));
assert_eq!(template.id, id);
+7
View File
@@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Public roster language is Pod. `/pod` is the customer surface; fleet remains
the internal wire, storage, and migration name (#5776).
- Provider catalogs: compatible hosts (Baseten, Groq, Cerebras, SenseNova,
Command Code) no longer compile a frozen model roster. Descriptors name the
wire, URL, and env; live `GET /v1/models` and a Codewhale-owned catalog
layer are the offering list. Command Code is a catalog/descriptor row, not
a `ProviderKind`. Catalog presence is not an availability, entitlement, or
provider-acceptance claim (#5783).
- Provider selection no longer probes or adopts an external CLI credential on
ordinary picker use. Reuse requires an explicit "Use external CLI credentials"
choice, exact-path confirmation, and Codewhale-owned revoke (#5772).
+4 -2
View File
@@ -95,8 +95,10 @@ fn exact_builtin_provider(provider_id: &str) -> Option<ApiProvider> {
const fn catalog_provenance(source: &CatalogSource) -> &'static str {
match source {
CatalogSource::Bundled => "bundled catalog",
CatalogSource::Live { .. } | CatalogSource::ModelsDevLive { .. } => "live catalog",
CatalogSource::Bundled | CatalogSource::CodewhaleBundled { .. } => "bundled catalog",
CatalogSource::Live { .. }
| CatalogSource::ModelsDevLive { .. }
| CatalogSource::CodewhaleLive { .. } => "live catalog",
CatalogSource::ConfigOverride | CatalogSource::UserOverride => "override",
}
}
+11 -4
View File
@@ -2019,7 +2019,10 @@ fn offering_for_row(row: &ModelPickerRow) -> Option<codewhale_config::catalog::C
fn offering_fetched_at(row: &ModelPickerRow) -> u64 {
match offering_for_row(row).map(|o| o.source) {
Some(CatalogSource::Live { fetched_at, .. }) => fetched_at,
Some(
CatalogSource::Live { fetched_at, .. }
| CatalogSource::CodewhaleLive { fetched_at, .. },
) => fetched_at,
_ => 0,
}
}
@@ -2316,10 +2319,14 @@ fn render_picker_model_hint(
PickerPricing::Unknown => parts.push("price unknown".to_string()),
}
match metadata.source.as_ref() {
Some(CatalogSource::Live { .. } | CatalogSource::ModelsDevLive { .. }) => {
parts.push("live".to_string())
Some(
CatalogSource::Live { .. }
| CatalogSource::ModelsDevLive { .. }
| CatalogSource::CodewhaleLive { .. },
) => parts.push("live".to_string()),
Some(CatalogSource::Bundled | CatalogSource::CodewhaleBundled { .. }) => {
parts.push("bundled".to_string())
}
Some(CatalogSource::Bundled) => parts.push("bundled".to_string()),
Some(CatalogSource::ConfigOverride | CatalogSource::UserOverride) => {
parts.push("override".to_string())
}
+7 -1
View File
@@ -4418,7 +4418,8 @@ impl ModalView for ProviderPickerView {
Stage::StepfunBillingRoute => 11,
Stage::Confirm => 10,
Stage::CustomForm => 12,
Stage::TemplateList => 16,
// Catalog rows + descriptor detail (URL, env, default, docs).
Stage::TemplateList => 18,
};
let popup_area = centered_modal_area(area, 120, preferred_height, 64, 8);
@@ -6108,11 +6109,16 @@ mod tests {
assert!(rendered.contains("OpenCode Zen"), "{rendered}");
assert!(rendered.contains("OpenCode Go"), "{rendered}");
assert!(rendered.contains("SenseNova"), "{rendered}");
assert!(rendered.contains("Command Code"), "{rendered}");
assert!(rendered.contains("Agnes"), "{rendered}");
assert!(
rendered.contains("no published") || rendered.contains("unpublished"),
"{rendered}"
);
assert!(
rendered.contains("https://opencode.ai/zen/v1"),
"descriptor endpoint missing: {rendered}"
);
assert!(
rendered.contains("https://opencode.ai/docs/zen/"),
"{rendered}"