revert: openai-codex goes back to the compiled-in catalog

Reverts c61977c. The live listing needed no codex CLI on the user's
machine — kigi issued the request itself and `client_version` was a
compiled-in string — but the project keeps the catalog in the binary, so
the fetch path, the `OpenaiCodex` listing dialect, its parser and the
pinned client version are gone again, and the codex `User-Agent` returns
to its previous value.

The models.dev provider rename `kimi-for-coding` → `kimi-code-plan-cn`
rode in the same commit and stays: the regenerated snapshot ships under
the new id.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-21 18:36:46 -04:00
1 parent 95d4f19650
commit a690987ea2
4 files changed
+238 -211

No files matched your search

@@ -363,6 +363,19 @@ fn fetch_one_platform_models(
bearer: &str,
enrichment: &kigi_models::enrichment::EnrichmentCatalog,
) -> Result<(Vec<crate::agent::config::ModelEntryConfig>, Option<String>), BackendError> {
// A platform that serves NO live `/models` listing (openai-codex) delivers a
// HARDCODED catalog: short-circuit BEFORE any HTTP, mapping the compiled-in
// `WireModel`s through the SAME `platform_wire_model_to_entry` output a live
// listing produces (context window + per-model reasoning efforts). The
// bearer is unused here (login gates availability; no request is made).
if let Some(wire_models) = platform.hardcoded_catalog() {
let base_url = platform_fetch_base(platform, endpoints);
let models = wire_models
.into_iter()
.map(|wire| platform_wire_model_to_entry(platform, wire, &base_url))
.collect();
return Ok((models, None));
}
let client = crate::http::shared_blocking_client();
let url = match platform.listing() {
kigi_models::ListingDialect::OpenAi => platform_models_url(platform, endpoints),
@@ -371,13 +384,6 @@ fn fetch_one_platform_models(
kigi_models::ListingDialect::Anthropic => {
format!("{}?limit=1000", platform_models_url(platform, endpoints))
}
// The Codex backend REQUIRES client_version (400 without it) and gates
// the catalog on it: an old version is answered with a truncated list.
kigi_models::ListingDialect::OpenaiCodex => format!(
"{}?client_version={}",
platform_models_url(platform, endpoints),
kigi_sampling_types::CODEX_CLIENT_VERSION
),
};
tracing::info!(platform = platform.as_str(), url = %url, "fetching platform models");
let request = match platform.key_header() {
@@ -470,18 +476,6 @@ fn fetch_one_platform_models(
}
})?
}
// The Codex backend serves `{models:[...]}` with its own availability
// fields; the adapter keeps the shown + API-served models and carries
// context window and per-model reasoning levels.
kigi_models::ListingDialect::OpenaiCodex => {
let body = response.text()?;
kigi_models::parse_openai_codex_listing(&body).map_err(|e| {
BackendError::RequestFailed {
status: 200,
body: format!("codex listing parse failed: {e}"),
}
})?
}
};
// Canonicalize listing ids before filtering/enrichment/keying. Google's
// OpenAI-compat `/models` returns `models/`-prefixed ids while its chat
@@ -1477,36 +1471,25 @@ mod tests {
);
}
/// openai-codex e2e: the catalog is LIVE. The request carries the pinned
/// `client_version` (the backend 400s without it and truncates the list for
/// an old one) and the session bearer; the hidden model is dropped, and the
/// kept ones are keyed `openai-codex/<slug>` on the Responses backend with
/// their wire context window and efforts (`ultra`, which Kigi has no level
/// for, is dropped).
/// openai-codex fetch: the catalog is HARDCODED, so the fetch path
/// short-circuits BEFORE any HTTP — there is NO mock `/models` server, yet
/// the fetch returns exactly the 4 compiled-in models keyed
/// `openai-codex/<slug>` on the Responses backend, ctx 272000, each exposing
/// its exact reasoning efforts (incl. the codex-only `xhigh`/`max`).
/// A BOGUS base URL confirms no live `/models` request is attempted (it would
/// otherwise fail against an unroutable host).
#[tokio::test(flavor = "multi_thread")]
#[serial_test::serial]
async fn openai_codex_catalog_comes_from_the_live_listing() {
const BODY: &str = r#"{"models":[{"slug":"gpt-6-astra","display_name":"GPT-6-Astra","visibility":"list","supported_in_api":true,"context_window":272000,"default_reasoning_level":"medium","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"},{"effort":"xhigh"},{"effort":"max"},{"effort":"ultra"}],"input_modalities":["text","image"]},{"slug":"gpt-reserve","display_name":"GPT-Reserve","visibility":"hide","supported_in_api":true,"context_window":272000,"default_reasoning_level":"medium","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"},{"effort":"xhigh"},{"effort":"max"}],"input_modalities":["text","image"]},{"slug":"gpt-5.5","display_name":"GPT-5.5","visibility":"list","supported_in_api":true,"context_window":272000,"default_reasoning_level":"medium","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"},{"effort":"xhigh"}],"input_modalities":["text","image"]}]}"#;
let codex_server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/models"))
.and(wiremock::matchers::query_param(
"client_version",
kigi_sampling_types::CODEX_CLIENT_VERSION,
))
.and(wiremock::matchers::header(
"Authorization",
"Bearer codex-session-tok",
))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_raw(BODY, "application/json"),
)
.expect(1)
.mount(&codex_server)
.await;
let _base =
kigi_test_support::EnvGuard::set(kigi_models::CODEX_BASE_URL_ENV, codex_server.uri());
async fn openai_codex_catalog_is_hardcoded_with_no_http_fetch() {
// Unroutable base: if the fetch path tried a live `/models` request it
// would error here; the hardcoded short-circuit ignores it for fetching.
let _base = kigi_test_support::EnvGuard::set(
kigi_models::CODEX_BASE_URL_ENV,
"http://127.0.0.1:1/codex",
);
let endpoints = crate::agent::config::EndpointsConfig::default();
// The session token merely marks openai-codex "enabled"; it is unused by
// the hardcoded path (no request rides it).
let mut oauth_tokens = OAuthSessionTokens::new();
oauth_tokens.insert(
kigi_models::PlatformId::OpenaiCodex,
@@ -1518,7 +1501,7 @@ mod tests {
})
.await
.unwrap()
.expect("openai-codex listing fetch must succeed");
.expect("openai-codex hardcoded catalog fetch must succeed with no HTTP");
assert_eq!(
result
@@ -1526,35 +1509,57 @@ mod tests {
.iter()
.map(|m| m.id.as_deref().unwrap_or_default())
.collect::<Vec<_>>(),
vec!["openai-codex/gpt-6-astra", "openai-codex/gpt-5.5"],
"hidden models never reach the picker"
vec![
"openai-codex/gpt-5.6-sol",
"openai-codex/gpt-5.6-terra",
"openai-codex/gpt-5.6-luna",
"openai-codex/gpt-5.5",
],
"exactly the 4 hardcoded models, keyed openai-codex/<slug>"
);
let astra = &result.models[0];
// Excluded models never appear.
for absent in [
"openai-codex/gpt-5.3-codex-spark",
"openai-codex/gpt-5.4",
"openai-codex/gpt-5.4-mini",
"openai-codex/codex-auto-review",
] {
assert!(
!result
.models
.iter()
.any(|m| m.id.as_deref() == Some(absent)),
"{absent} must be absent from the hardcoded catalog"
);
}
let sol = &result.models[0];
assert_eq!(
astra.api_backend,
sol.api_backend,
crate::sampling::ApiBackend::Responses,
"openai-codex speaks the Responses wire"
);
assert_eq!(astra.context_window.get(), 272_000);
assert_eq!(astra.name.as_deref(), Some("GPT-6-Astra"));
assert_eq!(sol.context_window.get(), 272_000);
assert_eq!(sol.name.as_deref(), Some("GPT-5.6-Sol"));
assert!(
astra.supported_in_api,
"openai-codex carries its OWN pooled credential — it must NOT be gated on the primary (Kimi) session"
sol.supported_in_api,
"openai-codex carries its OWN pooled credential — it must NOT be \
gated on the primary (Kimi) session"
);
assert!(
visible_to_non_primary_session_user(astra),
"a ChatGPT/Codex-only user has no primary OAuth session; their models must still appear in the picker"
visible_to_non_primary_session_user(sol),
"a ChatGPT/Codex-only user has no primary OAuth session; their \
models must still appear in the picker"
);
assert!(astra.supports_reasoning_effort);
assert!(sol.supports_reasoning_effort);
assert_eq!(
astra
.reasoning_efforts
sol.reasoning_efforts
.iter()
.map(|o| o.id.as_str())
.collect::<Vec<_>>(),
vec!["low", "medium", "high", "xhigh", "max"],
"`ultra` has no Kigi level and is dropped"
"sol exposes the full codex effort menu"
);
// gpt-5.5 tops out at xhigh (no max).
let five_five = result
.models
.iter()