M1/F1: Kimi Code OAuth device-code flow
Replace the xAI OAuth stack with the Kimi device authorization grant:
- kimi_oauth.rs wire layer (device_authorization + token poll + refresh
against kigi_env::oauth_host(); client_id per PRD; retryable statuses
429/5xx with backoff; expired_token restarts authorization)
- X-Msh-Device-{Name,Model,Id} headers; device_id minted uuid4-hex at
~/.kigi/device_id (0600)
- Storage: system keyring service `kigi`, entry `oauth/kimi-code`
(macOS/Windows native backends), atomic-file fallback under ~/.kigi;
official client's keyring/~/.kimi never touched
- Refresh manager: 60s tick, threshold max(300, expires_in*0.5),
401-tombstone keyed by rejected refresh token with 300s cooldown and
rotation auto-clear, cross-process lock with sibling-adoption
triple-check, sleep/wake forced refresh
- Deleted xAI machinery: enterprise OIDC (PKCE/JWKS/teams), devbox login,
external auth provider, JWT tier gating + subscription paywall stack,
X-XAI-Token-Auth marker headers, ZDR gates, /user enrichment
- kigi login / TUI /login both drive the device flow; login-host display
now derives from kigi_env::oauth_host()
- 264 auth unit/wiremock tests; live contract probe of
auth.kimi.com/api/oauth/device_authorization matches the wire shapes
Gates: check/clippy --all-targets clean, fmt, deny ok, kigi-shell lib
5131 tests green.
This commit is contained in:
@@ -454,32 +454,21 @@ pub(super) fn handle_credit_limit_recheck_complete(
|
||||
agent_id: AgentId,
|
||||
meta: Option<serde_json::Value>,
|
||||
) -> Vec<Effect> {
|
||||
let old_tier = app.subscription_tier.clone();
|
||||
if let Some(meta_val) = meta
|
||||
&& let Ok(auth_meta) = serde_json::from_value::<kigi_shell::auth::AuthMeta>(meta_val)
|
||||
{
|
||||
app.apply_auth_meta(&auth_meta);
|
||||
}
|
||||
let tier_changed = app.subscription_tier != old_tier && app.subscription_tier.is_some();
|
||||
|
||||
let Some(agent) = app.agents.get_mut(&agent_id) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
// If the user already submitted another prompt while the
|
||||
// recheck was in flight, don't retry the stashed one — they've
|
||||
// moved on. The tier update (above) still takes effect.
|
||||
// recheck was in flight, don't show the upsell — they've moved on.
|
||||
let user_moved_on = !agent.session.state.is_idle() || !agent.session.pending_prompts.is_empty();
|
||||
|
||||
if tier_changed && !user_moved_on {
|
||||
if let Some(prompt) = agent.credit_limit_stashed_prompt.take() {
|
||||
let tier_name = app.subscription_tier.as_deref().unwrap_or("a higher tier");
|
||||
agent.scrollback.push_block(RenderBlock::system(format!(
|
||||
"Subscription upgraded to {tier_name}. Retrying\u{2026}"
|
||||
)));
|
||||
agent.session.enqueue_in_flight_prompt_front(prompt);
|
||||
}
|
||||
} else if !user_moved_on {
|
||||
if !user_moved_on {
|
||||
let balance = agent
|
||||
.credit_balance
|
||||
.as_ref()
|
||||
|
||||
Reference in New Issue
Block a user