M1/F2+F4: platform registry, Moonshot API-key channel, dynamic model sync

F2 — fixed three-platform registry in kigi-models: kimi-code
(subscription, OAuth bearer, base kigi_env::coding_api_base_url()),
moonshot-cn (https://api.moonshot.cn/v1), moonshot-ai
(https://api.moonshot.ai/v1) with kimi-k model-prefix filtering.
Moonshot API keys via KIGI_MOONSHOT_CN_API_KEY / KIGI_MOONSHOT_AI_API_KEY
(+ KIGI_MOONSHOT_API_KEY shared fallback) or ~/.kigi/config.toml;
values redacted from logs/display.

F4 — model catalog now syncs from GET {base}/models (Bearer auth,
wire shape per official kimi-cli: id/context_length/supports_reasoning/
supports_image_in/supports_video_in/display_name) with the official
capability-derivation rules (thinking / always_thinking-in-name /
kimi-k2 implicit set). Managed keys {platform_id}/{model_id}; default
model = first list entry; default thinking iff capabilities contain
thinking/always_thinking. Sync failure → last cache; no cache →
built-in fallback table seeded from ids sourced in official kimi-cli
(kimi-for-coding, kimi-k2-turbo-preview, kimi-k2-thinking-turbo).
401 during sync forces one token refresh and retries.

Model resolution priority preserved: CLI > env > config > server >
fallback. Grok model artifacts (grok-4*/grok-build catalog, tier
gating remnants) removed from non-test code.

All first-party endpoints re-verified live: device_authorization mints
real codes; /models on all three platforms answers with real API auth
errors when unauthenticated.

Gates: check/clippy --all-targets 0/0, fmt clean, deny ok,
kigi-shell lib 5136 green, kigi-tui lib 6819 green, kigi-models 8.
This commit is contained in:
2026-07-17 09:23:44 -04:00
parent 021b82443d
commit fe1f885bb3
25 changed files with 2148 additions and 430 deletions
@@ -1805,7 +1805,7 @@ fn format_session_info_hides_model_hash_for_noncoding_without_flag() {
}
#[test]
fn format_session_info_shows_model_hash_for_coding_slug_without_flag() {
let mut info = make_session_info("grok-build", None, 1000, 10000);
let mut info = make_session_info("kimi-for-coding", None, 1000, 10000);
info.data.model_fingerprint = Some("abc123".into());
info.data.show_model_fingerprint = false;
let text = format_session_info(&info, None, false);
@@ -26,11 +26,13 @@ pub const PROMPT_SUGGESTIONS_ENV: &str = "KIGI_PROMPT_SUGGESTIONS";
/// `KIGI_PROMPT_SUGGESTIONS_MODEL=<model-id>`.
pub const PROMPT_SUGGESTIONS_MODEL_ENV: &str = "KIGI_PROMPT_SUGGESTIONS_MODEL";
/// Preferred model for suggestion calls when the server catalog offers it
/// (cheap + fast). The session model is never used: when this is absent
/// from the catalog the request carries no model hint and the shell
/// resolves (or skips) it — see [`resolve_model`].
pub const PREFERRED_SUGGESTION_MODEL: &str = "grok-build-0.1";
/// Preferred model for suggestion calls when the server catalog offers it.
/// The session model is never used: when this is absent from the catalog the
/// request carries no model hint and the shell resolves (or skips) it — see
/// [`resolve_model`].
pub fn preferred_suggestion_model() -> &'static str {
kigi_shell::models::default_model()
}
/// Controller for the predicted-next-prompt ghost text.
#[derive(Debug, Default)]
@@ -165,11 +167,11 @@ pub fn resolve_model(models: &crate::acp::model_state::ModelState) -> Option<Str
return Some(model);
}
let preferred =
agent_client_protocol::ModelId::new(std::sync::Arc::from(PREFERRED_SUGGESTION_MODEL));
agent_client_protocol::ModelId::new(std::sync::Arc::from(preferred_suggestion_model()));
models
.available
.contains_key(&preferred)
.then(|| PREFERRED_SUGGESTION_MODEL.to_owned())
.then(|| preferred_suggestion_model().to_owned())
}
#[cfg(test)]