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:
2026-07-17 07:37:29 -04:00
parent d6c20fc13f
commit 021b82443d
117 changed files with 4052 additions and 19900 deletions
@@ -19,15 +19,10 @@ pub struct CredentialSnapshot {
/// identity (`StaticAuthCredentialProvider`). Read by the OTel layer to
/// populate the `user.id` resource attribute.
pub user_id: Option<String>,
/// Team identifier from OAuth. `None` for personal accounts or when
/// no auth is configured.
pub team_id: Option<String>,
/// `uuidv5(NAMESPACE_OID, deployment_key)`, set only for deployment-key auth.
pub deployment_id: Option<String>,
/// `uuidv5(NAMESPACE_OID, api_key)`, set only for `AuthMode::ApiKey`.
pub api_key_id: Option<String>,
/// Org id from the OIDC `organizationId` claim; `None` for personal / deployment-key auth.
pub organization_id: Option<String>,
}
/// Source of truth for outbound auth on data-collector requests.
@@ -50,13 +45,6 @@ pub trait AuthCredentialProvider: HttpAuth + Send + Sync + 'static {
/// Returns `false` if no refresher is configured or refresh failed.
async fn refresh_after_unauthorized(&self) -> bool;
/// Whether `X-XAI-Token-Auth` should be sent with the bearer token.
/// `false` for deployment keys (bare Bearer), `true` for user/OAuth tokens.
/// See `GrokAuthCredentials::apply()` for the wire format contract.
fn needs_token_auth_header(&self) -> bool {
true
}
/// Whether the provider holds a credential worth a real outbound attempt —
/// an unexpired token (in memory or on disk), or a static key. Default
/// `true` always attempts.
+1 -1
View File
@@ -1,5 +1,5 @@
/// Apply auth headers to outbound visibility requests.
/// Implemented by `kigi-shell::util::grok_auth_credentials::GrokAuthCredentials`
/// Implemented by `kigi-shell::util::kigi_auth_credentials::KigiAuthCredentials`
/// to keep credential construction owned by shell while letting data-collector
/// build the request without reaching back into shell types.
pub trait HttpAuth: Send + Sync {