Add OpenAI platform: live model fetching with enrichment (provider 1)

The 4th registry row: id "openai", OPENAI_API_KEY env > auth.json
"openai" scope (login picker/paste/validation all registry-generic —
zero TUI changes needed, pinned by the picker test), base
https://api.openai.com/v1 with KIGI_OPENAI_BASE_URL override, Responses
dialect via the new PlatformWireApi spec field, enrichment-backed
metadata (wire_serves_metadata=false).

OpenAI's GET /v1/models returns bare ids and is polluted with
tts/whisper/embeddings entries: the listing is restricted to
enrichment-known TOOL-CALLING models (review caught that membership
alone admitted models.dev-known embeddings models, which would 400 on
every agentic request; dropped ids are debug-logged for launch-day
diagnosability). Context windows, effort menus, display names, and
thinking capability come from the enrichment pipeline — wiremock e2e
pins the full contract: polluted live listing + models.dev →
one Responses-backed chat model with a 400k documented context window.

Responses max-effort wiring (closes the P0c-1 debt): canonical effort
rides a CreateResponseWrapper sidecar and patch_reasoning_effort writes
it onto the serialized body at both send sites (all seven levels pinned,
xhigh/max distinct, summary preserved); normalize_effort_echo drops
echoes async-openai's typed enum cannot represent at both the non-stream
and SSE parse seams; the dead typed to_responses_api converter is
deleted. Kimi/moonshot stay byte-identical (ChatCompletions untouched,
wire_api maps to the same default; kimi wire tests green).

kimi-import now recognizes ANY registry platform host as built-in
(was hardcoded moonshot), covering openai and future rows.
This commit is contained in:
2026-07-21 05:04:55 -04:00
parent fdf9b956f5
commit 23e94939c0
10 changed files with 436 additions and 58 deletions
+68 -2
View File
@@ -42,6 +42,15 @@ fn env_or(var: &str, compiled: &str) -> String {
}
}
/// Inference dialect a platform speaks. Leaf-safe mirror of the sampler's
/// `ApiBackend` (kigi-models must stay dependency-light); the shell maps it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlatformWireApi {
ChatCompletions,
Responses,
Messages,
}
/// Where a platform's base URL is resolved from.
enum BaseUrlSource {
/// The Kimi Code subscription base, owned by `kigi_env`
@@ -91,6 +100,13 @@ struct PlatformSpec {
/// window / thinking metadata — enrichment (and its network refresh) is
/// skipped entirely for such platforms.
wire_serves_metadata: bool,
/// Inference dialect (mapped to the sampler backend by the shell).
wire_api: PlatformWireApi,
/// Restrict the live listing to models the enrichment catalog knows —
/// for providers whose `/models` is polluted with non-chat entries
/// (tts/embeddings/image). Availability still requires the LIVE listing;
/// this only drops listing noise, never adds models.
restrict_to_enriched: bool,
}
const KIMI_CODE_SPEC: PlatformSpec = PlatformSpec {
@@ -105,6 +121,8 @@ const KIMI_CODE_SPEC: PlatformSpec = PlatformSpec {
login_label: None,
models_dev_id: Some("kimi-for-coding"),
wire_serves_metadata: true,
wire_api: PlatformWireApi::ChatCompletions,
restrict_to_enriched: false,
};
const MOONSHOT_CN_SPEC: PlatformSpec = PlatformSpec {
@@ -122,6 +140,8 @@ const MOONSHOT_CN_SPEC: PlatformSpec = PlatformSpec {
login_label: Some("Moonshot Open Platform (API key \u{b7} moonshot.cn)"),
models_dev_id: Some("moonshotai-cn"),
wire_serves_metadata: true,
wire_api: PlatformWireApi::ChatCompletions,
restrict_to_enriched: false,
};
const MOONSHOT_AI_SPEC: PlatformSpec = PlatformSpec {
@@ -139,6 +159,32 @@ const MOONSHOT_AI_SPEC: PlatformSpec = PlatformSpec {
login_label: Some("Moonshot Open Platform (API key \u{b7} moonshot.ai)"),
models_dev_id: Some("moonshotai"),
wire_serves_metadata: true,
wire_api: PlatformWireApi::ChatCompletions,
restrict_to_enriched: false,
};
/// Base-URL override for OpenAI (dev/test escape hatch).
pub const OPENAI_BASE_URL_ENV: &str = "KIGI_OPENAI_BASE_URL";
const OPENAI_SPEC: PlatformSpec = PlatformSpec {
id: "openai",
display_name: "OpenAI",
base_url: BaseUrlSource::EnvOr {
env: OPENAI_BASE_URL_ENV,
default: "https://api.openai.com/v1",
},
uses_oauth: false,
allowed_model_prefixes: None,
api_key_envs: &["OPENAI_API_KEY"],
vendor: "OpenAI",
console_host: Some("platform.openai.com"),
login_label: Some("OpenAI (API key)"),
models_dev_id: Some("openai"),
// GET /v1/models returns bare ids only (no context/thinking metadata)
// and is polluted with tts/embeddings/image entries.
wire_serves_metadata: false,
wire_api: PlatformWireApi::Responses,
restrict_to_enriched: true,
};
/// The platform registry. Platforms are compiled-in spec rows; there is no
@@ -151,12 +197,19 @@ pub enum PlatformId {
MoonshotCn,
/// Moonshot AI open platform, api.moonshot.ai (API key).
MoonshotAi,
/// OpenAI platform API (API key, Responses dialect).
OpenAi,
}
impl PlatformId {
/// All platforms, in catalog precedence order: the subscription channel
/// 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; 4] = [
Self::KimiCode,
Self::MoonshotCn,
Self::MoonshotAi,
Self::OpenAi,
];
/// The registry row backing this platform (single source of per-platform
/// data; every accessor below reads it).
@@ -165,6 +218,7 @@ impl PlatformId {
Self::KimiCode => &KIMI_CODE_SPEC,
Self::MoonshotCn => &MOONSHOT_CN_SPEC,
Self::MoonshotAi => &MOONSHOT_AI_SPEC,
Self::OpenAi => &OPENAI_SPEC,
}
}
@@ -243,6 +297,17 @@ impl PlatformId {
pub fn wire_serves_metadata(self) -> bool {
self.spec().wire_serves_metadata
}
/// Inference dialect this platform speaks (shell maps to `ApiBackend`).
pub fn wire_api(self) -> PlatformWireApi {
self.spec().wire_api
}
/// Restrict the live listing to enrichment-known models (drops non-chat
/// listing noise on polluted providers). Never adds models.
pub fn restrict_to_enriched(self) -> bool {
self.spec().restrict_to_enriched
}
}
/// Split a managed catalog key `{platform_id}/{model_id}` back into its
@@ -573,9 +638,10 @@ mod tests {
PlatformId::KimiCode => 0,
PlatformId::MoonshotCn => 1,
PlatformId::MoonshotAi => 2,
PlatformId::OpenAi => 3,
}
}
const VARIANT_COUNT: usize = 3; // update together with `ordinal`
const VARIANT_COUNT: usize = 4; // update together with `ordinal`
let mut seen: Vec<usize> = PlatformId::ALL.iter().map(|&p| ordinal(p)).collect();
seen.sort_unstable();
seen.dedup();