Synced from monorepo

Synced from monorepo

Changes:
- Temporarily disable session share link creation in the TUI
- Do not approve plan on empty Enter from the revise prompt
- Expose chat product Skills via ACP available_commands_update
- Return immediately from a blocking wait on an already-completed ACP task
- Split headless pager module for clearer structure
- Stop git worktree prune from removing user registrations on resume
- Use compaction sampler tokenizer for item token counts
- Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE
- Cancel all session subagents when the user stops
- Let the session persistence actor exit when its session ends
- Make fullscreen terminal resize much cheaper on long sessions
- Report honestly from kill_task when an ACP task does not exist
- Hide /usage for external-auth deployments
- Forward the history-load trailer’s computer_reason to the client
- Remove ineffective no-op tool reminder
- Declare slash-command screen-mode support in one place
- Keep settings enum picker on the committed value until Enter
- Reap a PTY’s full process tree
- Stream tool calls from headless mode over ACP
- Bridge gateway task lifecycle to ACP for chat session background tasks
- Don’t warn about truncated history on a suppressed replay
- Fit full-replace summarizer input and recover on context-length errors
- Stop dropping agents over an unrecognized frontmatter color
- Add /undo as a slash alias for /rewind
- Harden sleep/wake token-refresh paths against forced re-login
- Add session/list ACP method
- Give each sampling backend its own conversion module
- Treat an unenrolled child process as a lint error
- Suppress the cancelled marker on send-now wake turns
- Stop tearing down Roslyn on every edit, and read C# diagnostics

Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
This commit is contained in:
grokkybara[bot] 2026-07-30 19:07:40 +00:00
commit dd04f397b1
367 changed files with 29489 additions and 10051 deletions

View file

@ -150,7 +150,8 @@ impl PromptUsage {
output_tokens,
total_tokens: _, // derived from input + output
cached_read_tokens,
reasoning_tokens: _, // subset of output_tokens
cache_creation_tokens, // subset of input_tokens on the wire
reasoning_tokens: _, // subset of output_tokens
model_calls,
api_duration_ms: _, // timing, not tokens
cost_usd_ticks: _, // cost without usage cannot occur
@ -161,6 +162,7 @@ impl PromptUsage {
&& input_tokens == 0
&& output_tokens == 0
&& cached_read_tokens == 0
&& cache_creation_tokens == 0
&& self.model_usage.is_empty()
}
}
@ -178,6 +180,10 @@ pub struct PromptUsageModel {
pub total_tokens: u64,
#[serde(default)]
pub cached_read_tokens: u64,
/// Cache-creation prompt tokens, folded into `input_tokens` on the ACP wire
/// but projected as a disjoint bucket in the headless shape.
#[serde(default)]
pub cache_creation_tokens: u64,
#[serde(default)]
pub reasoning_tokens: u64,
#[serde(default)]
@ -202,6 +208,23 @@ pub struct PromptUsageModel {
pub cost_missing_calls: u64,
}
/// One model call's token usage: the four Messages API `message.usage` fields
/// (`input_tokens` is the uncached prompt portion) plus `reasoning_tokens`.
/// Distinct from [`PromptUsageModel`], which sums the whole prompt.
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ResponseUsage {
#[serde(default)]
pub input_tokens: u64,
#[serde(default)]
pub output_tokens: u64,
#[serde(default)]
pub cache_read_input_tokens: u64,
#[serde(default)]
pub cache_creation_input_tokens: u64,
#[serde(default)]
pub reasoning_tokens: u64,
}
impl From<&xai_chat_state::UsageTotals> for PromptUsageModel {
fn from(t: &xai_chat_state::UsageTotals) -> Self {
// Exhaustive destructure: a new ledger field cannot silently miss the
@ -210,6 +233,7 @@ impl From<&xai_chat_state::UsageTotals> for PromptUsageModel {
input_tokens,
output_tokens,
cached_read_tokens,
cache_creation_tokens,
reasoning_tokens,
model_calls,
api_duration_ms,
@ -221,6 +245,7 @@ impl From<&xai_chat_state::UsageTotals> for PromptUsageModel {
output_tokens,
total_tokens: t.total_tokens(),
cached_read_tokens,
cache_creation_tokens,
reasoning_tokens,
model_calls,
api_duration_ms,
@ -263,8 +288,9 @@ pub fn uncached_input_tokens(full_input: u64, cached_read: u64) -> u64 {
/// Project usage onto a headless result object.
///
/// - `usage.input_tokens` = uncached (`full cache_read`); identity
/// `uncached + cache_read + output = total_tokens`.
/// - `usage.input_tokens` = uncached (`full cache_read cache_creation`), so
/// the three prompt buckets are disjoint; identity
/// `input_tokens + cache_read + cache_creation + output = total_tokens`.
/// - Omits all cost floats when partial or incomplete (absence ≠ free).
/// - Incomplete with no tokens emits only `usage_is_incomplete` (no zero usage object).
/// - `modelUsage` rows are a reduced external-compat schema (camelCase; no reasoning/duration).
@ -281,6 +307,7 @@ pub fn project_result_usage(result: &mut serde_json::Value, usage: &PromptUsage)
output_tokens,
total_tokens,
cached_read_tokens,
cache_creation_tokens,
reasoning_tokens,
model_calls: _, // totals-level; headless carries num_turns instead
api_duration_ms: _, // dropped: not part of the frozen headless shape
@ -289,8 +316,10 @@ pub fn project_result_usage(result: &mut serde_json::Value, usage: &PromptUsage)
cost_missing_calls: _, // internal partiality count; the flag suffices
} = usage.totals;
result["usage"] = serde_json::json!({
"input_tokens": uncached_input_tokens(input_tokens, cached_read_tokens),
"input_tokens": uncached_input_tokens(input_tokens, cached_read_tokens)
.saturating_sub(cache_creation_tokens),
"cache_read_input_tokens": cached_read_tokens,
"cache_creation_input_tokens": cache_creation_tokens,
"output_tokens": output_tokens,
"reasoning_tokens": reasoning_tokens,
"total_tokens": total_tokens,
@ -318,6 +347,7 @@ pub fn project_result_usage(result: &mut serde_json::Value, usage: &PromptUsage)
output_tokens,
total_tokens: _, // derivable per row
cached_read_tokens,
cache_creation_tokens,
reasoning_tokens: _, // dropped: reduced per-model schema
model_calls,
api_duration_ms: _, // dropped: reduced per-model schema
@ -326,9 +356,11 @@ pub fn project_result_usage(result: &mut serde_json::Value, usage: &PromptUsage)
cost_missing_calls: _,
} = *m;
let mut entry = serde_json::json!({
"inputTokens": uncached_input_tokens(input_tokens, cached_read_tokens),
"inputTokens": uncached_input_tokens(input_tokens, cached_read_tokens)
.saturating_sub(cache_creation_tokens),
"outputTokens": output_tokens,
"cacheReadInputTokens": cached_read_tokens,
"cacheCreationInputTokens": cache_creation_tokens,
"modelCalls": model_calls,
});
if !hide_costs
@ -972,6 +1004,60 @@ pub enum SessionUpdate {
#[serde(default, skip_serializing_if = "Option::is_none")]
usage: Option<PromptUsage>,
},
/// One model response opened (Messages `message_start`), carrying the real
/// message id, model, and input-side token counts. Rides the buffered chunk
/// rail so it is ordered AHEAD of this response's agent chunks: headless
/// partial-mode framing consumes it to emit the real `message_start` id and
/// input usage instead of a synthesized placeholder / zero-seeded usage.
/// Messages backend only; other backends never emit it (the reducer keeps
/// its placeholder fallback there).
///
/// `input_tokens` is the uncached prompt portion; `cache_read_input_tokens`
/// and `cache_creation_input_tokens` are the separate prompt-side cache
/// buckets, both known at `message_start` on the Messages backend.
ResponseStarted {
#[serde(default, skip_serializing_if = "Option::is_none")]
message_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
model: Option<String>,
#[serde(default)]
input_tokens: u64,
#[serde(default)]
cache_read_input_tokens: u64,
#[serde(default)]
cache_creation_input_tokens: u64,
},
/// This response's reasoning (thinking) block finished; carries its
/// encrypted signature. Rides the buffered chunk rail so it is ordered right
/// AFTER this response's thought chunks (and before its text): headless
/// partial-mode framing consumes it to emit `signature_delta` before the
/// thinking block's `content_block_stop`, in order. Messages backend only.
ReasoningCompleted {
#[serde(default, skip_serializing_if = "Option::is_none")]
signature: Option<String>,
},
/// One completed model response, so headless can emit a Messages API
/// assistant frame per response. Ordered with the response's chunks; a tool
/// loop emits several. The durable outcome rides `TurnCompleted`.
ResponseCompleted {
/// Provider message id (Messages `message.id`), when reported.
#[serde(default, skip_serializing_if = "Option::is_none")]
message_id: Option<String>,
/// Verbatim wire stop reason (`end_turn`, `tool_use`, …), when reported.
#[serde(default, skip_serializing_if = "Option::is_none")]
stop_reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
usage: Option<ResponseUsage>,
/// Reasoning signature (encrypted content) for this response's thinking.
#[serde(default, skip_serializing_if = "Option::is_none")]
signature: Option<String>,
/// The provider's matched stop sequence (Messages API
/// `message.stop_sequence`), present only when the model stopped on a
/// configured stop sequence; `None` otherwise. Headless
/// `streaming-messages-json` stamps it onto the assistant frame.
#[serde(default, skip_serializing_if = "Option::is_none")]
stop_sequence: Option<String>,
},
/// Catch-all for unrecognized session update types.
/// Allows forward/backward compatibility when variants are added or removed.
/// All fields from the unrecognized variant are discarded during deserialization.
@ -1057,8 +1143,12 @@ pub enum RetryState {
/// `legacy_auth` is intentionally excluded: those failures carry their own
/// detailed migration guidance (`grok logout` / `grok login`) in the
/// message, so we surface that verbatim instead of the generic prompt.
///
/// `auth_transient` is excluded for the opposite reason: the shell emits it
/// only when the failure self-heals (see `AuthManager::requires_manual_reauth`)
/// and the message already says it recovers on its own — no `/login` banner.
pub fn is_reauthable_failure(error_type: Option<&str>, message: &str) -> bool {
if error_type == Some("legacy_auth") {
if matches!(error_type, Some("legacy_auth") | Some("auth_transient")) {
return false;
}
error_type == Some("auth") || message.contains("Unauthorized (401)")