Refactor platform registry onto spec rows (providers P0a)

Replace PlatformId's per-method match arms with static PlatformSpec rows
read through a single spec() accessor. Zero behavior change (adversarially
reviewed: ids, display names, base URLs + env override pairing, oauth flag,
prefix filters, and api-key env precedence order are byte-identical; all
public signatures unchanged). Groundwork for the multi-provider expansion:
adding a platform becomes variant + ALL entry + spec() arm + row.

Guards added for the row-addition workflow: spec-id uniqueness test (parse
scans rows) and a compile-forced ALL-completeness test (a variant missing
from ALL would otherwise be silently unparseable and skipped by model
sync). Future-proofed two assertions that hardcoded 'openai' as an
unknown-platform example.
This commit is contained in:
2026-07-21 00:03:46 -04:00
parent c4fe0750e2
commit 99d99fb47a
+136 -38
View File
@@ -1,8 +1,8 @@
//! Kimi model catalog primitives (PRD F2/F4). //! Kimi model catalog primitives (PRD F2/F4).
//! //!
//! This crate owns: //! This crate owns:
//! - the fixed three-platform registry ([`PlatformId`]): the Kimi Code //! - the compiled-in platform registry ([`PlatformId`] + its spec rows): the
//! subscription channel plus the two Moonshot open platforms; //! Kimi Code subscription channel plus the API-key platforms;
//! - the `GET {base}/models` wire contract ([`WireModel`]) and the capability //! - the `GET {base}/models` wire contract ([`WireModel`]) and the capability
//! derivation ported from kimi-cli `auth/platforms.py`; //! derivation ported from kimi-cli `auth/platforms.py`;
//! - the managed catalog key format `{platform_id}/{model_id}`; //! - the managed catalog key format `{platform_id}/{model_id}`;
@@ -40,8 +40,77 @@ fn env_or(var: &str, compiled: &str) -> String {
} }
} }
/// The fixed platform registry. Kigi talks to exactly these three model /// Where a platform's base URL is resolved from.
/// providers; there is no dynamic provider registration (PRD F2). enum BaseUrlSource {
/// The Kimi Code subscription base, owned by `kigi_env`
/// (honors `KIGI_CODE_BASE_URL`).
KigiEnvCoding,
/// Fixed compiled default with a dev/test env-var override.
EnvOr {
env: &'static str,
default: &'static str,
},
}
/// One row of the platform registry. All per-platform data lives in these
/// rows; [`PlatformId`] methods only read fields. Adding a platform touches
/// exactly four sites in this file: the enum variant, the `ALL` entry, the
/// `spec()` arm, and the row (plus quirk code where a provider deviates).
/// The `spec()` arm is compiler-enforced; the `ALL` entry is enforced by the
/// `all_covers_every_variant` test — a variant missing from `ALL` would
/// otherwise be silently unparseable and excluded from model sync.
struct PlatformSpec {
/// Wire id (auth method id, managed-model-key prefix, config key).
id: &'static str,
display_name: &'static str,
base_url: BaseUrlSource,
/// True for OAuth-bearer subscription channels.
uses_oauth: bool,
/// Model-id prefixes admitted from this platform's `/models` listing.
/// `None` = no filtering (listing served pre-filtered).
allowed_model_prefixes: Option<&'static [&'static str]>,
/// Env var names holding this platform's API key, in precedence order
/// (first set, non-blank value wins). Empty for OAuth channels.
///
/// SECURITY: the *values* behind these names must never be logged.
api_key_envs: &'static [&'static str],
}
const KIMI_CODE_SPEC: PlatformSpec = PlatformSpec {
id: "kimi-code",
display_name: "Kimi Code",
base_url: BaseUrlSource::KigiEnvCoding,
uses_oauth: true,
allowed_model_prefixes: None,
api_key_envs: &[],
};
const MOONSHOT_CN_SPEC: PlatformSpec = PlatformSpec {
id: "moonshot-cn",
display_name: "Moonshot AI Open Platform (moonshot.cn)",
base_url: BaseUrlSource::EnvOr {
env: MOONSHOT_CN_BASE_URL_ENV,
default: "https://api.moonshot.cn/v1",
},
uses_oauth: false,
allowed_model_prefixes: Some(&["kimi-k"]),
api_key_envs: &[MOONSHOT_CN_API_KEY_ENV, MOONSHOT_API_KEY_ENV],
};
const MOONSHOT_AI_SPEC: PlatformSpec = PlatformSpec {
id: "moonshot-ai",
display_name: "Moonshot AI Open Platform (moonshot.ai)",
base_url: BaseUrlSource::EnvOr {
env: MOONSHOT_AI_BASE_URL_ENV,
default: "https://api.moonshot.ai/v1",
},
uses_oauth: false,
allowed_model_prefixes: Some(&["kimi-k"]),
api_key_envs: &[MOONSHOT_AI_API_KEY_ENV, MOONSHOT_API_KEY_ENV],
};
/// The platform registry. Platforms are compiled-in spec rows; there is no
/// dynamic provider registration (PRD F2).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PlatformId { pub enum PlatformId {
/// Kimi Code subscription (OAuth bearer from the F1 device flow). /// Kimi Code subscription (OAuth bearer from the F1 device flow).
@@ -57,55 +126,48 @@ impl PlatformId {
/// first so "default model = first list item" favors it when present. /// first so "default model = first list item" favors it when present.
pub const ALL: [PlatformId; 3] = [Self::KimiCode, Self::MoonshotCn, Self::MoonshotAi]; pub const ALL: [PlatformId; 3] = [Self::KimiCode, Self::MoonshotCn, Self::MoonshotAi];
pub fn as_str(self) -> &'static str { /// The registry row backing this platform (single source of per-platform
/// data; every accessor below reads it).
const fn spec(self) -> &'static PlatformSpec {
match self { match self {
Self::KimiCode => "kimi-code", Self::KimiCode => &KIMI_CODE_SPEC,
Self::MoonshotCn => "moonshot-cn", Self::MoonshotCn => &MOONSHOT_CN_SPEC,
Self::MoonshotAi => "moonshot-ai", Self::MoonshotAi => &MOONSHOT_AI_SPEC,
} }
} }
pub fn as_str(self) -> &'static str {
self.spec().id
}
pub fn parse(s: &str) -> Option<Self> { pub fn parse(s: &str) -> Option<Self> {
match s { Self::ALL.into_iter().find(|p| p.spec().id == s)
"kimi-code" => Some(Self::KimiCode),
"moonshot-cn" => Some(Self::MoonshotCn),
"moonshot-ai" => Some(Self::MoonshotAi),
_ => None,
}
} }
pub fn display_name(self) -> &'static str { pub fn display_name(self) -> &'static str {
match self { self.spec().display_name
Self::KimiCode => "Kimi Code",
Self::MoonshotCn => "Moonshot AI Open Platform (moonshot.cn)",
Self::MoonshotAi => "Moonshot AI Open Platform (moonshot.ai)",
}
} }
/// Inference/model-listing base URL. The subscription base honors the /// Inference/model-listing base URL. The subscription base honors the
/// `KIGI_CODE_BASE_URL` override via [`kigi_env::coding_api_base_url`]; /// `KIGI_CODE_BASE_URL` override via [`kigi_env::coding_api_base_url`];
/// the open-platform bases are fixed in production, with /// API-key platform bases are fixed in production, with a per-platform
/// `KIGI_MOONSHOT_{CN,AI}_BASE_URL` as dev/test overrides. /// env var (e.g. `KIGI_MOONSHOT_{CN,AI}_BASE_URL`) as dev/test override.
pub fn base_url(self) -> String { pub fn base_url(self) -> String {
match self { match self.spec().base_url {
Self::KimiCode => kigi_env::coding_api_base_url(), BaseUrlSource::KigiEnvCoding => kigi_env::coding_api_base_url(),
Self::MoonshotCn => env_or(MOONSHOT_CN_BASE_URL_ENV, "https://api.moonshot.cn/v1"), BaseUrlSource::EnvOr { env, default } => env_or(env, default),
Self::MoonshotAi => env_or(MOONSHOT_AI_BASE_URL_ENV, "https://api.moonshot.ai/v1"),
} }
} }
/// True for the OAuth-bearer subscription channel. /// True for OAuth-bearer subscription channels.
pub fn uses_oauth(self) -> bool { pub fn uses_oauth(self) -> bool {
matches!(self, Self::KimiCode) self.spec().uses_oauth
} }
/// Model-id prefixes admitted from this platform's `/models` listing. /// Model-id prefixes admitted from this platform's `/models` listing.
/// `None` = no filtering (subscription listing is served pre-filtered). /// `None` = no filtering (subscription listing is served pre-filtered).
pub fn allowed_model_prefixes(self) -> Option<&'static [&'static str]> { pub fn allowed_model_prefixes(self) -> Option<&'static [&'static str]> {
match self { self.spec().allowed_model_prefixes
Self::KimiCode => None,
Self::MoonshotCn | Self::MoonshotAi => Some(&["kimi-k"]),
}
} }
/// Env var names holding this platform's API key, in precedence order /// Env var names holding this platform's API key, in precedence order
@@ -113,11 +175,7 @@ impl PlatformId {
/// ///
/// SECURITY: the *values* behind these names must never be logged. /// SECURITY: the *values* behind these names must never be logged.
pub fn api_key_env_names(self) -> &'static [&'static str] { pub fn api_key_env_names(self) -> &'static [&'static str] {
match self { self.spec().api_key_envs
Self::KimiCode => &[],
Self::MoonshotCn => &[MOONSHOT_CN_API_KEY_ENV, MOONSHOT_API_KEY_ENV],
Self::MoonshotAi => &[MOONSHOT_AI_API_KEY_ENV, MOONSHOT_API_KEY_ENV],
}
} }
/// Managed catalog key for a model served by this platform: /// Managed catalog key for a model served by this platform:
@@ -441,7 +499,47 @@ mod tests {
for p in PlatformId::ALL { for p in PlatformId::ALL {
assert_eq!(PlatformId::parse(p.as_str()), Some(p)); assert_eq!(PlatformId::parse(p.as_str()), Some(p));
} }
assert_eq!(PlatformId::parse("openai"), None); assert_eq!(PlatformId::parse("not-a-platform"), None);
}
/// A variant missing from `ALL` compiles fine (`ALL`'s length is a plain
/// literal) but is silently unparseable and excluded from model sync.
/// The exhaustive match below fails compilation when a variant is added,
/// forcing this test — and with it the `ALL` entry — to be updated.
#[test]
fn all_covers_every_variant() {
fn ordinal(p: PlatformId) -> usize {
match p {
PlatformId::KimiCode => 0,
PlatformId::MoonshotCn => 1,
PlatformId::MoonshotAi => 2,
}
}
const VARIANT_COUNT: usize = 3; // update together with `ordinal`
let mut seen: Vec<usize> = PlatformId::ALL.iter().map(|&p| ordinal(p)).collect();
seen.sort_unstable();
seen.dedup();
assert_eq!(
seen.len(),
PlatformId::ALL.len(),
"duplicate variant in ALL"
);
assert_eq!(
seen.len(),
VARIANT_COUNT,
"PlatformId::ALL must contain every variant"
);
}
/// `parse` resolves by scanning spec rows, so duplicate ids would
/// silently shadow a platform. Pin uniqueness as rows are added.
#[test]
fn platform_spec_ids_unique() {
let mut ids: Vec<&str> = PlatformId::ALL.iter().map(|p| p.as_str()).collect();
ids.sort_unstable();
let before = ids.len();
ids.dedup();
assert_eq!(ids.len(), before, "duplicate platform spec id");
} }
#[test] #[test]
@@ -473,7 +571,7 @@ mod tests {
); );
// No prefix / unknown platform / empty model id → None. // No prefix / unknown platform / empty model id → None.
assert_eq!(parse_managed_model_key("kimi-for-coding"), None); assert_eq!(parse_managed_model_key("kimi-for-coding"), None);
assert_eq!(parse_managed_model_key("openai/gpt"), None); assert_eq!(parse_managed_model_key("not-a-platform/gpt"), None);
assert_eq!(parse_managed_model_key("moonshot-cn/"), None); assert_eq!(parse_managed_model_key("moonshot-cn/"), None);
} }