F3: Kimi inference pipeline + full grok cloud-surface excision

Sampler / inference (PRD F3):
- kimi_compat.rs: single adaptation point for the Kimi chat/completions
  dialect (thinking-field mapping, model_id stripping, empty-content
  tool-call message fix, stream_options.include_usage), with kimi-cli
  source citations
- Rate-limit handling reworked for Kimi/Moonshot semantics; UA kigi/{version}
- /models replaces the xAI models-v2 endpoint everywhere; idle model
  refresh carries X-Msh-* device headers only (X-XAI-Token-Auth and
  x-grok-client-mode/CLIENT_MODE_HEADER machinery deleted)

Cloud-surface excision (PRD §5, zero-egress):
- remote/ conversations lane, cli-chat-proxy-types crate, prod/ dir,
  share command, credit bar: deleted (single local session lane;
  paginate() replaces merge_and_paginate)
- Subscription/tier gate stack deleted end-to-end: AppView
  gate/tier/team/ZDR fields, app/subscription.rs watch loop,
  dispatch/billing.rs paywall + SuperGrok upsell, free-usage-exhausted
  chain, tier-restricted commands, GateInfo, RemoteSettings gate fields,
  SettingsUpdateNotification gate fields
- /privacy + coding-data-sharing setting deleted (backed by a dead xAI
  RPC; Kigi is zero-egress — nothing to share or retain remotely)

Auth UX correctness (user-reported):
- Device-flow fixtures now mirror the live Kimi payload shape
  (https://www.kimi.com/code/authorize_device?user_code=..., verified
  against auth.kimi.com); the fabricated auth.kimi.com/device?code=...
  URLs are gone
- open_browser_detached is a no-op under cfg(test): unit tests drove
  wiremock fixture URLs into the real browser (root cause of the
  "garbage mock link" ABCD-1234 tabs)
- Welcome/pager-minimal rebrand: Grok Build -> Kigi, grok.com ->
  kimi.com, "Sign in to Grok" -> "Sign in to Kimi"
This commit is contained in:
2026-07-17 16:05:51 -04:00
parent fe1f885bb3
commit ea0ce9d15f
231 changed files with 4730 additions and 26358 deletions
@@ -1,7 +1,6 @@
//! Prompt and bash-command submission dispatchers and reload-window helpers.
use super::auth::{scrollback_has_recent_context_too_large, scrollback_has_recent_reauth_prompt};
use super::billing::is_credit_limit_error;
use super::ctx::with_active_agent;
use super::interject;
use super::permissions::drain_permission_queue;
@@ -292,7 +291,6 @@ pub(super) fn dispatch_send_prompt_inner(
return vec![];
};
// Capture app-level fields before the mut-borrow on `agent`.
let coding_data_sharing_opt_out_from_app = app.coding_data_retention_opt_out;
let show_tips_from_app = app.show_tips;
let auto_update_from_app = app.auto_update;
let respect_manual_folds_from_app = app.appearance.scrollback.scroll.respect_manual_folds;
@@ -324,37 +322,6 @@ pub(super) fn dispatch_send_prompt_inner(
let mut effects = Vec::new();
// ── Tier-restricted command upsell ─────────────────────────────
// Restricted commands (`/usage`, `/imagine`, …) are hidden from the
// registry's `get()`, so a typed invocation would otherwise fall
// through the unknown-command path below and leak to the model as a
// raw prompt. Upsell instead; genuinely unknown commands still pass
// through (shell/ACP commands depend on that).
if !literal
&& trimmed.starts_with('/')
&& let Some(invocation) = crate::slash::parse_invocation(trimmed)
&& agent
.prompt
.slash_controller
.registry()
.is_restricted(invocation.token)
{
// Only consume the composer when the upsell can actually open: with
// another question modal already up, `open_supergrok_upsell` would
// no-op and wiping the composer here would silently drop the typed
// text. Keep it instead so the user can resubmit after closing the
// modal — and never fall through to passthrough for restricted
// commands.
if agent.question_view.is_none() {
if consume_input {
agent.prompt.set_text("");
}
let opened = super::billing::open_restricted_command_upsell(agent);
debug_assert!(opened, "no modal was open, so the upsell must open");
}
return vec![];
}
// ── Registry-based slash command execution ─────────────────────
// If the text starts with `/`, run it through the slash registry.
// The registry resolves builtins, ACP-advertised commands, and
@@ -384,7 +351,6 @@ pub(super) fn dispatch_send_prompt_inner(
.iter()
.map(|(id, info)| (info.name.clone(), id.clone()))
.collect(),
coding_data_sharing_opt_out: coding_data_sharing_opt_out_from_app,
// Prefer optimistic pending over confirmed active.
plan_mode_active: agent.plan_mode_pending.unwrap_or(agent.plan_mode_active),
show_tips: show_tips_from_app,
@@ -1004,29 +970,11 @@ pub(super) fn handle_prompt_response(
None => expected_send_now.is_some(),
};
let rate_limited = agent.session.rate_limited;
// Fallback mirroring the credit-limit race guard below: if the retry
// notification lost the race with (or never reached) this
// PromptResponse, detect the free-usage code from the prompt error
// itself — the flattened 429 body embeds it.
let free_usage_blocked = agent.session.free_usage_blocked
|| result
.as_ref()
.err()
.is_some_and(|e| super::billing::is_free_usage_exhausted_error(e));
let model_incompatible = agent.session.model_incompatible;
// Context overflow: the RetryState handler already pushed the actionable
// block, so the generic TurnFailed + error toast are redundant. Derived
// from the scrollback (mirrors reauth), not a session flag.
let context_overflow = scrollback_has_recent_context_too_large(&agent.scrollback);
// Fallback: if the retry notification didn't set the flag,
// detect credit-limit denials (legacy 403 or pool 402) from
// the PromptResponse error + HTTP status. Covers races where
// the retry notification arrives after the PromptResponse.
let credit_limit_blocked = agent.session.credit_limit_blocked
|| result
.as_ref()
.err()
.is_some_and(|e| is_credit_limit_error(http_status, e));
// A 401/auth failure already surfaced an actionable
// `ReAuthRequired` prompt via the RetryState handler (which
// runs before this PromptResponse). Suppress the redundant
@@ -1055,12 +1003,7 @@ pub(super) fn handle_prompt_response(
);
}
// Stash the complete in-flight prompt before finish_turn clears it.
// Used by CreditLimitRecheckComplete to retry after a tier upgrade.
if credit_limit_blocked {
agent.credit_limit_stashed_prompt = agent.session.in_flight_prompt.clone();
}
// Likewise, stash the prompt from a turn that failed on an
// Stash the prompt from a turn that failed on an
// expired login (401 / re-auth). The AuthComplete handler
// auto-resubmits it after a successful mid-session re-auth.
// A non-rewindable turn (None) must not clobber an earlier stash.
@@ -1110,17 +1053,11 @@ pub(super) fn handle_prompt_response(
elapsed: Some(elapsed.unwrap_or_default()),
}),
(Err(_), _)
if rate_limited
|| free_usage_blocked
|| model_incompatible
|| credit_limit_blocked
|| reauth_prompted
|| context_overflow =>
if rate_limited || model_incompatible || reauth_prompted || context_overflow =>
{
// Skip TurnFailed when a dedicated prompt/modal shows instead
// (rate limit, free-usage paywall, model incompatibility,
// credit 403, 401 re-auth, or a terminal context-window
// overflow).
// (rate limit, model incompatibility, 401 re-auth, or a
// terminal context-window overflow).
None
}
(Err(err), _) => Some(SessionEvent::TurnFailed {
@@ -1147,9 +1084,7 @@ pub(super) fn handle_prompt_response(
}
(Err(err), _)
if !rate_limited
&& !free_usage_blocked
&& !model_incompatible
&& !credit_limit_blocked
&& !reauth_prompted
&& !context_overflow =>
{
@@ -1256,7 +1191,7 @@ pub(super) fn handle_prompt_response(
// Predicted-next-prompt (tab autocomplete): wipe any stale suggestion
// at every turn boundary. This must run before the reconnect /
// credit-limit early returns below, which skip the fetch gate
// paywall early returns below, which skip the fetch gate
// entirely — a prior ghost would otherwise survive those paths.
agent.prompt.prompt_suggestion.clear();
@@ -1271,60 +1206,6 @@ pub(super) fn handle_prompt_response(
return vec![];
}
// Credit-limit (403 legacy / 402 pool): strip stale error
// blocks, then do a one-shot subscription re-check. If the
// tier changed (user upgraded mid-session), the stashed
// prompt is retried automatically; otherwise the upsell
// is shown.
if credit_limit_blocked {
// Strip stale "Retry failed" / "Turn failed" error blocks
// that were pushed before the credit-limit was detected.
// Walk backwards from the end and remove matching events.
let mut to_remove = Vec::new();
for idx in (0..agent.scrollback.len()).rev() {
match agent.scrollback.entry(idx).map(|e| &e.block) {
Some(crate::scrollback::block::RenderBlock::SessionEvent(ev))
if matches!(
&ev.event,
SessionEvent::RetryFailed { .. } | SessionEvent::TurnFailed { .. }
) =>
{
to_remove.push(idx);
}
// Stop at the first non-error block.
Some(
crate::scrollback::block::RenderBlock::SessionEvent(_)
| crate::scrollback::block::RenderBlock::System(_),
) => continue,
_ => break,
}
}
for idx in to_remove {
agent.scrollback.remove_from(idx);
}
// Defer the upsell until the subscription re-check
// completes. Queue drain + billing fetch happen in the
// CreditLimitRecheckComplete handler.
if let Some(p) = pending_adoption {
agent.discard_pending_adoption_updates(&p.prompt_id);
}
return vec![Effect::CreditLimitRecheck { agent_id }];
}
// Free-usage paywall (429 + subscription:free-usage-exhausted): the
// RetryState handler set the flag and suppressed the generic
// rate-limit block; show the upsell modal. Driver-only by
// construction — viewers never receive a PromptResponse. No queue
// drain: queued prompts would fail on the same exhausted quota.
if free_usage_blocked {
super::billing::open_free_usage_upsell(agent);
if let Some(p) = pending_adoption {
agent.discard_pending_adoption_updates(&p.prompt_id);
}
return vec![];
}
// FIFO handoff: if a server-authoritative prompt drained
// into the running slot during this turn's teardown, adopt it
// now (finish_turn cleared current_prompt_id) and run the
@@ -1372,10 +1253,6 @@ pub(super) fn handle_prompt_response(
});
}
effects.push(Effect::FetchBilling {
agent_id,
silent: true,
});
return effects;
}
vec![]