This commit is contained in:
2026-07-22 11:08:54 -04:00
parent 422e241e13
commit 2d00a4e6e6
19 changed files with 1955 additions and 296 deletions
@@ -1917,6 +1917,51 @@ pub(crate) fn resolve_catalog_key(
.map(|(key, _)| acp::ModelId::new(key.clone()))
}
/// The managed catalog key (`{platform}/{model}`) a routing slug belongs to.
///
/// H5: `SamplingConfig::model` is the BARE routing slug, never the catalog key,
/// and duplicate slugs across platforms are BY DESIGN — the registry guarantees
/// an API-key platform and its subscription-OAuth twin list the SAME ids
/// (`xai`/`xai-grok`, `anthropic`/`claude-pro-max`, `openai`/`openai-codex`),
/// and `PlatformId::ALL` orders every API-key platform FIRST. A slug scan
/// therefore resolves the WRONG platform for a user holding both credentials:
/// the OAuth twin loses its live `bearer_resolver` (no mid-session refresh → an
/// unrecoverable 401 ~1h in), its Messages adaptation and its Copilot/Codex
/// identity headers.
///
/// `current_key` — [`ModelsManager::current_model_id`], the catalog key the
/// picker actually selected — is therefore authoritative whenever it names this
/// slug. Anything else (aux models, subagent overrides, unlisted slugs) falls
/// back to the picker's OWN lookup, [`resolve_catalog_key`], so the auth layer
/// and the picker can never resolve different entries.
pub(crate) fn managed_key_for_slug(
models: &IndexMap<String, ModelEntry>,
current_key: Option<&str>,
slug: &str,
) -> Option<String> {
if let Some(entry) = current_key.and_then(|key| models.get(key))
&& (entry.info.model == slug || current_key == Some(slug))
{
return entry.info.id.clone();
}
let key = resolve_catalog_key(models, &acp::ModelId::new(slug.to_string()))?;
models.get(key.0.as_ref())?.info.id.clone()
}
/// The registry platform a routing slug resolves to, via [`managed_key_for_slug`].
/// `None` for a bare / `[model.*]` / unlisted model. Single definition shared by
/// the session actor's inference-auth chokepoints and the aux/summary paths, so
/// the gate, the manager and the wire adaptations can never disagree.
pub(crate) fn platform_for_slug(
models: &IndexMap<String, ModelEntry>,
current_key: Option<&str>,
slug: &str,
) -> Option<kigi_models::PlatformId> {
let key = managed_key_for_slug(models, current_key, slug);
kigi_models::parse_managed_model_key(key.as_deref().unwrap_or(slug))
.map(|(platform, _)| platform)
}
/// Catalog key for a persisted session model id, restricted to **selectable**
/// entries. A selectable exact-key match wins (as in [`resolve_catalog_key`]);
/// otherwise the last selectable entry whose routing slug matches `id`, so a
@@ -3668,6 +3713,52 @@ mod tests {
assert!(!info.visible_for_auth(false));
}
/// SHIP-BLOCKER regression: a user who signed in with ONLY a Claude Pro/Max
/// subscription has no PRIMARY (Kimi) session, so `is_session_auth()` is
/// false. Stamping `supported_in_api = !uses_oauth()` therefore hid every
/// one of their models — `available()` returned an empty picker and the
/// whole subscription-OAuth feature was dead for its target user. The
/// claude-pro-max entry must be visible with no primary session at all.
#[test]
fn claude_pro_max_only_user_sees_their_models_in_the_picker() {
let wire: kigi_models::WireModel =
serde_json::from_value(serde_json::json!({ "id": "claude-opus-4-8" }))
.expect("wire model fixture");
let entry_config = crate::agent::models_fetch::platform_wire_model_to_entry(
kigi_models::PlatformId::ClaudeProMax,
wire,
"https://api.anthropic.com/v1",
);
let entry = ModelEntry::from_config_entry(&entry_config);
let key = entry
.info
.id
.clone()
.expect("platform entries carry a managed catalog key");
assert_eq!(key, "claude-pro-max/claude-opus-4-8");
let mut catalog = IndexMap::new();
catalog.insert(key.clone(), entry);
// Empty home ⇒ the primary AuthManager holds no credential at all.
let home = tempfile::tempdir().expect("tempdir");
let mgr = ModelsManager::new(
None,
catalog,
acp::ModelId::new(Arc::from(key.clone())),
Arc::new(AuthManager::new(home.path(), KimiCodeConfig::default())),
config::Config::default(),
);
assert!(
!mgr.is_session_auth(),
"a claude-pro-max-only user has no PRIMARY (Kimi) OAuth session"
);
assert!(
mgr.available()
.contains_key(&acp::ModelId::new(Arc::from(key.clone()))),
"the claude-pro-max model must reach the picker without a primary session"
);
}
// ── duplicate model slug re-keying (A/B experiment "auto" alias) ──
fn make_entry_config(model: &str, name: Option<&str>) -> config::ModelEntryConfig {