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
@@ -13,11 +13,10 @@ use crate::prompt_images::{InlineMediaInfo, ScrollbackImageRef, ScrollbackVideoR
use super::blocks::mermaid_content::DiagramAffordance;
use super::blocks::{
AgentMessageBlock, BgTaskBlock, BtwBlock, ContextInfoBlock, CreditLimitBlock,
EditToolCallBlock, ExecuteToolCallBlock, LineRange, ListDirToolCallBlock, OtherToolCallBlock,
ReadToolCallBlock, SearchFileMatch, SearchToolCallBlock, SessionEvent, SessionEventBlock,
SubagentBlock, SubagentBlockKind, SystemMessageBlock, ThinkingBlock, ToolCallBlock,
UserPromptBlock,
AgentMessageBlock, BgTaskBlock, BtwBlock, ContextInfoBlock, EditToolCallBlock,
ExecuteToolCallBlock, LineRange, ListDirToolCallBlock, OtherToolCallBlock, ReadToolCallBlock,
SearchFileMatch, SearchToolCallBlock, SessionEvent, SessionEventBlock, SubagentBlock,
SubagentBlockKind, SystemMessageBlock, ThinkingBlock, ToolCallBlock, UserPromptBlock,
};
use super::types::{
AccentStyle, BlockBackground, BlockContext, BlockOutput, DisplayMode, RenderedBlockOutput,
@@ -383,8 +382,6 @@ pub enum RenderBlock {
Btw(BtwBlock),
/// `/context` snapshot with categorical bar + breakdown.
ContextInfo(ContextInfoBlock),
/// Credit-limit card for max-tier users (red accent, single action).
CreditLimit(CreditLimitBlock),
}
/// Delegate a method call to the inner block variant.
@@ -402,7 +399,6 @@ macro_rules! delegate_block {
RenderBlock::Subagent(b) => b.$method($($arg),*),
RenderBlock::Btw(b) => b.$method($($arg),*),
RenderBlock::ContextInfo(b) => b.$method($($arg),*),
RenderBlock::CreditLimit(b) => b.$method($($arg),*),
}
};
}
@@ -778,15 +774,6 @@ impl RenderBlock {
RenderBlock::SessionEvent(SessionEventBlock::new(event))
}
/// Create a credit-limit card (inline scrollback block for max-tier users).
pub fn credit_limit_card(
heading: impl Into<String>,
action: crate::scrollback::blocks::CreditLimitCardAction,
url: impl Into<String>,
) -> Self {
RenderBlock::CreditLimit(CreditLimitBlock::new(heading, action, url))
}
/// Create a "Task started" background task block.
pub fn bg_task(command: impl Into<String>, task_id: impl Into<String>) -> Self {
RenderBlock::BgTask(BgTaskBlock::started(command, task_id))
@@ -912,11 +899,6 @@ impl RenderBlock {
matches!(self, RenderBlock::AgentMessage(_))
}
/// Check if this block is a CreditLimit card.
pub fn is_credit_limit(&self) -> bool {
matches!(self, RenderBlock::CreditLimit(_))
}
/// Check if this block is a plan mode tool call (enter or exit).
///
/// Exact-matches the canonical tool-name set rather than substring-matching
@@ -1014,10 +996,9 @@ impl RenderBlock {
None
}
}
RenderBlock::System(_)
| RenderBlock::SessionEvent(_)
| RenderBlock::ContextInfo(_)
| RenderBlock::CreditLimit(_) => None,
RenderBlock::System(_) | RenderBlock::SessionEvent(_) | RenderBlock::ContextInfo(_) => {
None
}
RenderBlock::Btw(_) => Some(theme.accent_plan),
RenderBlock::Stub(block) => Some(block.accent_color),
}
@@ -1161,9 +1142,6 @@ impl RenderBlock {
Some(b.content().rendered_plain_text()),
]),
RenderBlock::ContextInfo(b) => join_searchable([Some(b.model.clone())]),
RenderBlock::CreditLimit(b) => {
join_searchable([Some(b.heading.clone()), Some(b.url.clone())])
}
RenderBlock::ToolCall(tc) => tc.searchable_text(),
}
}
@@ -1566,18 +1544,6 @@ mod searchable_text_tests {
assert_eq!(block.searchable_text().as_deref(), Some("grok-4.5"));
}
#[test]
fn credit_limit_indexes_heading_and_url() {
let block = RenderBlock::credit_limit_card(
"credit limit reached",
crate::scrollback::blocks::CreditLimitCardAction::EnablePayg,
"https://grok.com?_s=usage",
);
let text = block.searchable_text().expect("credit limit text");
assert!(text.contains("credit limit reached"), "got: {text:?}");
assert!(text.contains("https://grok.com?_s=usage"), "got: {text:?}");
}
#[test]
fn search_tool_indexes_pattern_and_match_line() {
let block = RenderBlock::search(
@@ -1,253 +0,0 @@
//! CreditLimitBlock — scrollback card shown when a max-tier user exhausts credits.
//!
//! Replaces the Q&A question modal for users already at the highest tier
//! (SuperGrok Heavy). Instead of offering "Upgrade tier" + PAYG / buy-credits
//! options in the question overlay, this block renders an inline card with a
//! descriptive message and a link to the usage/billing page.
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use crate::scrollback::block::BlockContent;
use crate::scrollback::types::{AccentStyle, BlockContext, BlockLine, BlockOutput, DisplayMode};
use crate::theme::Theme;
/// Which continue-path the max-tier credit-limit card recommends.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CreditLimitCardAction {
/// Legacy on-demand: PAYG not enabled yet.
EnablePayg,
/// Legacy on-demand: PAYG on but at spending cap.
IncreasePaygLimit,
/// Unified usage billing: purchase prepaid credits.
PurchaseCredits,
}
/// Inline scrollback card for credit-limit exhaustion on max-tier accounts.
#[derive(Debug, Clone)]
pub struct CreditLimitBlock {
/// Card heading (e.g. "You've hit your free credits limit.").
pub heading: String,
/// Continue-path body copy selector.
pub action: CreditLimitCardAction,
/// URL to the usage/billing page.
pub url: String,
}
impl CreditLimitBlock {
/// Create a new credit-limit card.
pub fn new(
heading: impl Into<String>,
action: CreditLimitCardAction,
url: impl Into<String>,
) -> Self {
Self {
heading: heading.into(),
action,
url: url.into(),
}
}
}
impl BlockContent for CreditLimitBlock {
fn output(&self, _ctx: &BlockContext) -> BlockOutput {
let theme = Theme::current();
// Heading in bold warning color (amber/yellow).
let heading_style = Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD);
let heading = Line::from(Span::styled(self.heading.clone(), heading_style));
// Body copy — contextual message based on billing mode.
let muted = theme.muted();
let body = match self.action {
CreditLimitCardAction::IncreasePaygLimit => {
"You can continue by increasing your spending limit."
}
CreditLimitCardAction::EnablePayg => {
"You can continue by enabling pay-as-you-go usage."
}
CreditLimitCardAction::PurchaseCredits => {
"You can continue by purchasing more credits."
}
};
let body_line = Line::from(Span::styled(body.to_string(), muted));
// Clickable link styled as a button.
let link_style = theme.link_style();
let link_line = Line::from(vec![Span::styled(self.url.clone(), link_style)]);
BlockOutput {
lines: vec![
BlockLine::styled(heading).with_selection_range(Some(0)),
BlockLine::separator(Line::from("")),
BlockLine::styled(body_line).with_selection_range(Some(0)),
BlockLine::styled(link_line).with_selection_range(Some(0)),
],
}
}
fn accent(&self, _ctx: &BlockContext) -> Option<AccentStyle> {
let theme = Theme::current();
Some(AccentStyle::static_color(theme.warning))
}
fn has_vpad(&self, _ctx: &BlockContext) -> bool {
true
}
fn has_raw_mode(&self) -> bool {
false
}
fn is_foldable(&self) -> bool {
false
}
fn default_display_mode(&self) -> DisplayMode {
DisplayMode::Expanded
}
fn is_selectable(&self) -> bool {
true
}
fn is_groupable(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::appearance::AppearanceConfig;
fn ctx() -> BlockContext {
BlockContext {
mode: DisplayMode::Expanded,
is_running: false,
width: 80,
raw: false,
max_lines: None,
appearance: AppearanceConfig::default(),
is_selected: false,
cwd: None,
}
}
#[test]
fn output_payg_off_mentions_enabling() {
let block = CreditLimitBlock::new(
"You\u{2019}ve hit your credit limit.",
CreditLimitCardAction::EnablePayg,
"https://grok.com?_s=usage",
);
let output = block.output(&ctx());
let all_text: String = output
.lines
.iter()
.flat_map(|l| l.content.spans.iter().map(|s| s.content.as_ref()))
.collect();
assert!(all_text.contains("credit limit"));
assert!(all_text.contains("enabling pay-as-you-go"));
assert!(all_text.contains("grok.com?_s=usage"));
}
#[test]
fn output_payg_on_mentions_increasing() {
let block = CreditLimitBlock::new(
"You\u{2019}ve hit your spending cap.",
CreditLimitCardAction::IncreasePaygLimit,
"https://grok.com?_s=usage",
);
let output = block.output(&ctx());
let all_text: String = output
.lines
.iter()
.flat_map(|l| l.content.spans.iter().map(|s| s.content.as_ref()))
.collect();
assert!(all_text.contains("spending cap"));
assert!(all_text.contains("increasing your spending limit"));
assert!(all_text.contains("grok.com?_s=usage"));
}
#[test]
fn output_unified_mentions_purchasing_credits() {
let block = CreditLimitBlock::new(
"You hit your weekly limit.",
CreditLimitCardAction::PurchaseCredits,
"https://grok.com?_s=usage",
);
let output = block.output(&ctx());
let all_text: String = output
.lines
.iter()
.flat_map(|l| l.content.spans.iter().map(|s| s.content.as_ref()))
.collect();
assert!(all_text.contains("purchasing more credits"));
assert!(all_text.contains("grok.com?_s=usage"));
}
#[test]
fn has_warning_accent() {
let block = CreditLimitBlock::new("heading", CreditLimitCardAction::EnablePayg, "url");
let accent = block.accent(&ctx());
let theme = Theme::current();
assert!(accent.is_some());
assert_eq!(accent.unwrap().color, theme.warning);
}
#[test]
fn block_content_contract() {
let block = CreditLimitBlock::new("heading", CreditLimitCardAction::EnablePayg, "url");
let c = ctx();
assert!(!block.is_foldable());
assert!(block.is_selectable());
assert!(!block.is_groupable());
assert!(matches!(
block.default_display_mode(),
DisplayMode::Expanded
));
assert!(block.has_vpad(&c));
assert!(!block.has_raw_mode());
}
#[test]
fn output_structure_and_content() {
let url = "https://grok.com?_s=usage";
let block = CreditLimitBlock::new("Test heading", CreditLimitCardAction::EnablePayg, url);
let output = block.output(&ctx());
// heading, separator, body, link = 4 lines
assert_eq!(output.lines.len(), 4);
let all_text: String = output
.lines
.iter()
.flat_map(|l| l.content.spans.iter().map(|s| s.content.as_ref()))
.collect();
assert!(all_text.contains(url));
// Heading uses bold modifier.
assert!(
output.lines[0]
.content
.spans
.iter()
.any(|s| s.style.add_modifier.contains(Modifier::BOLD))
);
}
#[test]
fn new_stores_fields_correctly() {
let block = CreditLimitBlock::new(
"my heading",
CreditLimitCardAction::IncreasePaygLimit,
"https://example.com",
);
assert_eq!(block.heading, "my heading");
assert_eq!(block.action, CreditLimitCardAction::IncreasePaygLimit);
assert_eq!(block.url, "https://example.com");
}
}
@@ -6,7 +6,6 @@ mod agent;
mod bg_task;
mod btw;
mod context_info;
mod credit_limit;
pub mod markdown_content;
pub mod mermaid_content;
mod quote_bar;
@@ -21,7 +20,6 @@ pub use agent::AgentMessageBlock;
pub use bg_task::{BgTaskBlock, BgTaskKind};
pub use btw::BtwBlock;
pub use context_info::ContextInfoBlock;
pub use credit_limit::{CreditLimitBlock, CreditLimitCardAction};
pub use session_event::{EndWork, SessionEvent, SessionEventBlock};
pub use subagent::{SubagentBlock, SubagentBlockKind};
pub use system::SystemMessageBlock;
@@ -60,7 +60,7 @@ pub fn render_blocks_to_markdown<'a>(blocks: impl IntoIterator<Item = &'a Render
last_was_agent = false;
}
// Skip all non-conversation chrome: Thinking, System, SessionEvent, BgTask,
// Subagent, Btw, CreditLimit, Stub, etc. Thinking blocks are
// Subagent, Btw, Stub, etc. Thinking blocks are
// treated as intra-Assistant glue (no new header).
_ => {}
}
@@ -526,7 +526,7 @@ impl<'a> EntryRenderer<'a> {
///
/// EXACT for blocks whose searchable text mirrors their selectable rendered
/// lines (plain source blocks, markdown/thinking bodies); a best-effort
/// estimate for field-joined source (Subagent/BgTask/CreditLimit), kept on
/// estimate for field-joined source (Subagent/BgTask/etc.), kept on
/// screen by the caller's entry-height clamp. Past the last logical line,
/// clamps to the final content row.
pub fn rendered_row_of_logical_line(&self, width: u16, logical_line: usize) -> u16 {