M0: compilable skeleton — Kigi 0.1.0 fork surgery

Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.

Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
  kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
  ptyctl, ptyctl-cli, third_party/ unchanged; proto package
  xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
  KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
  (templates re-encrypted)

Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
  trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
  module & dc_log, heap-profile uploader, auth-diagnostics uploader,
  session-analytics halves of feedback; local zero-egress observability
  preserved in new kigi-log crate (unified log, --debug firehose,
  subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
  direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
  relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
  ~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
  kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
  session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
  shell util

Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
  https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
  https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
  Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted

Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
  workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
  all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
  exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
  insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean

Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
  (new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
  fast-worktree); RSS measurement tests serialized via serial_test

Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
  notices sustained; kigi-tools ported-code notices extended; README,
  CONTRIBUTING, SECURITY, AGENTS.md rewritten

Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 changed files with 1353757 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
//! Per-prompt and per-session billing ledgers (not serialized).
//!
//! `total_tokens()` is input + output: Responses wire `total` is live context
//! length. Compaction and other side calls never call `record_main_loop_call`.
//!
//! # Completeness ownership
//!
//! Wire incomplete is the OR of these stores (each has a distinct role):
//!
//! - **`UsageLedger.incomplete`** — durable on the bill snapshot. Set by nested
//! subagent incomplete fold, drain timeout, true apply-miss, and
//! `mark_usage_incomplete`. Monotonic for a ledger instance.
//! - **Sticky (`subagent_usage_not_applied` on the coordinator)** — pin-scoped
//! **report** signal (session-only attribution or apply-miss report). Not a
//! second token sink; does not stain ledgers by itself.
//! - **Foreground live IDs** — fold may still land; freeze drains ≤120s or fails
//! closed. Cancel skips multi-second drain (actor-loop safety).
//! - **Background live** — never waits; prompt report incomplete immediately;
//! spend still folds into the session ledger at completion (no session-ledger
//! incomplete).
//!
//! Freeze and cancel share one outcome policy: ledger marks only on fail-closed;
//! sticky and background_live are report-level only.
//!
//! Projection (`PromptUsage`) never invents tokens; it only ORs completeness
//! and scrubs costs when partial or incomplete.
use indexmap::IndexMap;
use kigi_sampling_types::TokenUsage;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct UsageTotals {
pub input_tokens: u64,
pub output_tokens: u64,
pub cached_read_tokens: u64,
pub reasoning_tokens: u64,
pub model_calls: u64,
pub api_duration_ms: u64,
/// USD ticks (1e10 per USD). Absent when no call reported cost.
pub cost_usd_ticks: Option<i64>,
pub cost_missing_calls: u64,
}
impl UsageTotals {
fn from_call(
usage: &TokenUsage,
api_duration_ms: Option<u64>,
cost_usd_ticks: Option<i64>,
) -> Self {
let cost_usd_ticks = kigi_sampling_types::reported_cost_ticks(cost_usd_ticks);
Self {
input_tokens: u64::from(usage.prompt_tokens),
output_tokens: u64::from(usage.completion_tokens),
cached_read_tokens: u64::from(usage.cached_prompt_tokens),
reasoning_tokens: u64::from(usage.reasoning_tokens),
model_calls: 1,
api_duration_ms: api_duration_ms.unwrap_or(0),
cost_usd_ticks,
cost_missing_calls: u64::from(cost_usd_ticks.is_none()),
}
}
pub fn total_tokens(&self) -> u64 {
self.input_tokens.saturating_add(self.output_tokens)
}
pub fn cost_is_partial(&self) -> bool {
self.cost_usd_ticks.is_some() && self.cost_missing_calls > 0
}
fn fold_totals(&mut self, other: &UsageTotals) {
let Self {
input_tokens,
output_tokens,
cached_read_tokens,
reasoning_tokens,
model_calls,
api_duration_ms,
cost_usd_ticks,
cost_missing_calls,
} = other;
self.input_tokens = self.input_tokens.saturating_add(*input_tokens);
self.output_tokens = self.output_tokens.saturating_add(*output_tokens);
self.cached_read_tokens = self.cached_read_tokens.saturating_add(*cached_read_tokens);
self.reasoning_tokens = self.reasoning_tokens.saturating_add(*reasoning_tokens);
self.model_calls = self.model_calls.saturating_add(*model_calls);
self.api_duration_ms = self.api_duration_ms.saturating_add(*api_duration_ms);
self.cost_missing_calls = self.cost_missing_calls.saturating_add(*cost_missing_calls);
self.cost_usd_ticks = merge_cost_ticks(self.cost_usd_ticks, *cost_usd_ticks);
}
}
fn merge_cost_ticks(a: Option<i64>, b: Option<i64>) -> Option<i64> {
match (a, b) {
(None, None) => None,
(a, b) => Some(a.unwrap_or(0).saturating_add(b.unwrap_or(0))),
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct UsageLedger {
pub totals: UsageTotals,
pub by_model: IndexMap<String, UsageTotals>,
/// Main-agent loop rounds for `num_turns` (subagents excluded).
pub main_loop_model_calls: u64,
/// Bill may under-count (drain timeout, nested subagent incomplete, apply failure).
pub incomplete: bool,
}
impl UsageLedger {
/// Fold one main-agent-loop model call. This is the only writer of
/// `main_loop_model_calls` (the wire `numTurns`); side calls such as
/// compaction must not use it.
pub fn record_main_loop_call(
&mut self,
model_id: &str,
usage: &TokenUsage,
api_duration_ms: Option<u64>,
cost_usd_ticks: Option<i64>,
) {
let call = UsageTotals::from_call(usage, api_duration_ms, cost_usd_ticks);
self.main_loop_model_calls = self.main_loop_model_calls.saturating_add(1);
self.fold_entry(model_id, &call);
}
/// Fold subagent usage without incrementing `main_loop_model_calls`.
pub fn record_subagent(&mut self, by_model: &[(String, UsageTotals)], incomplete: bool) {
for (model_id, totals) in by_model {
self.fold_entry(model_id, totals);
}
if incomplete {
self.incomplete = true;
}
}
pub fn mark_incomplete(&mut self) {
self.incomplete = true;
}
fn fold_entry(&mut self, model_id: &str, totals: &UsageTotals) {
self.totals.fold_totals(totals);
self.by_model
.entry(model_id.to_owned())
.or_default()
.fold_totals(totals);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tu(prompt: u32, completion: u32) -> TokenUsage {
TokenUsage {
prompt_tokens: prompt,
completion_tokens: completion,
total_tokens: 999_999,
reasoning_tokens: 0,
cached_prompt_tokens: 0,
}
}
#[test]
fn ledger_sums_partial_subagent_and_zero_cost() {
let mut ledger = UsageLedger::default();
ledger.record_main_loop_call("m", &tu(1, 1), None, Some(0));
assert_eq!(ledger.totals.cost_usd_ticks, None);
assert_eq!(ledger.totals.cost_missing_calls, 1);
ledger.record_main_loop_call("a", &tu(100, 10), Some(100), None);
ledger.record_main_loop_call("a", &tu(50, 5), Some(50), Some(70));
assert_eq!(ledger.totals.cost_usd_ticks, Some(70));
assert!(ledger.totals.cost_is_partial());
assert_eq!(ledger.main_loop_model_calls, 3);
ledger.record_subagent(
&[(
"b".into(),
UsageTotals {
input_tokens: 5,
model_calls: 1,
..Default::default()
},
)],
false,
);
assert_eq!(ledger.by_model["b"].input_tokens, 5);
assert_eq!(ledger.main_loop_model_calls, 3);
assert_eq!(ledger.totals.model_calls, 4);
assert!(!ledger.incomplete);
ledger.record_subagent(&[], true);
assert!(ledger.incomplete);
}
}