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:
@@ -0,0 +1,247 @@
|
||||
//! Trigger decision and result types for intra-compaction.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use super::config::{IntraCompactionConfig, IntraCompactionMode};
|
||||
|
||||
/// Information about why intra-compaction was triggered.
|
||||
///
|
||||
/// Constructed by [`should_compact`] and threaded through to
|
||||
/// [`crate::compact`] and the agent's event stream.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntraCompactionTrigger {
|
||||
/// Token count of the prompt most recently sent to the model.
|
||||
pub last_prompt_tokens: u32,
|
||||
/// Context window of the agent's current sampler (`max_len`).
|
||||
pub context_window: u32,
|
||||
/// `last_prompt_tokens / context_window` as an integer percentage,
|
||||
/// clamped to [0, 100].
|
||||
pub percent: u8,
|
||||
/// Step index (0-based) at which the trigger fired.
|
||||
pub step: u32,
|
||||
}
|
||||
|
||||
/// Result of a successful compaction.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntraCompactionResult {
|
||||
/// Sum of tokens in the turns that were compacted.
|
||||
pub tokens_before: u32,
|
||||
/// Tokens in the resulting compaction turn (the LLM summary).
|
||||
pub tokens_after: u32,
|
||||
/// Number of accumulated turns that were replaced.
|
||||
pub turns_compacted: u32,
|
||||
/// End-to-end elapsed time (decision → apply).
|
||||
pub elapsed: Duration,
|
||||
/// The summary text the LLM produced — the developer-turn content that
|
||||
/// replaced the compacted turns (for `HistoryThenSteps`, both passes'
|
||||
/// summaries joined). Carried so callers can record the actual result
|
||||
/// (e.g. as a developer turn in the thinking trace). `Arc<str>` because the
|
||||
/// summary can be large and is cloned along with the event downstream.
|
||||
pub summary: Arc<str>,
|
||||
}
|
||||
|
||||
/// Errors that can occur during intra-compaction.
|
||||
///
|
||||
/// All errors are non-fatal — the caller should log and continue without
|
||||
/// compaction. Worst case the next sampling call may fail with 400, which
|
||||
/// is the same as today (no compaction support at all).
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IntraCompactionError {
|
||||
/// The accumulated turn list has nothing meaningful to compact.
|
||||
/// Triggered when:
|
||||
/// - `get_accumulated_turns_for_compaction()` returns empty
|
||||
/// - `select_turns_to_compact()` finds nothing reducible (below
|
||||
/// `min_compactable_tokens` or no safe split point)
|
||||
#[error("nothing to compact")]
|
||||
NothingToCompact,
|
||||
|
||||
/// The compaction LLM call timed out with no usable output.
|
||||
#[error("compaction LLM call timed out")]
|
||||
Timeout,
|
||||
|
||||
/// The compaction LLM returned an empty response.
|
||||
#[error("compaction LLM returned empty response")]
|
||||
EmptyResponse,
|
||||
|
||||
/// Compaction result was not smaller than the original by the configured
|
||||
/// minimum (`max_reduction_ratio`).
|
||||
#[error("insufficient reduction: {tokens_after} > {tokens_before} * ratio")]
|
||||
InsufficientReduction {
|
||||
tokens_before: u32,
|
||||
tokens_after: u32,
|
||||
},
|
||||
|
||||
/// `apply_steps_compaction` received an invalid `n_turns_to_remove`
|
||||
/// (greater than the current accumulated-turn count). Parser state is
|
||||
/// left unchanged.
|
||||
#[error("invalid split: requested {requested}, only {available} available")]
|
||||
InvalidSplit { requested: usize, available: usize },
|
||||
|
||||
/// The parser variant does not support intra-compaction.
|
||||
#[error("intra-compaction not supported by this parser variant")]
|
||||
Unsupported,
|
||||
|
||||
/// LLM sampler construction failed.
|
||||
#[error("compaction sampler build failed: {0}")]
|
||||
SamplerBuild(String),
|
||||
|
||||
/// LLM sampler call could not be started.
|
||||
#[error("compaction sampler start failed: {0}")]
|
||||
SamplerStart(String),
|
||||
|
||||
/// LLM sampler emitted an error mid-stream.
|
||||
#[error("compaction sampler error: {0}")]
|
||||
SamplerStream(String),
|
||||
|
||||
/// `apply_steps_compaction` failed for a parser-specific reason
|
||||
/// (e.g. SglangEngine rebuild error).
|
||||
#[error("apply failed: {0}")]
|
||||
Apply(String),
|
||||
}
|
||||
|
||||
/// Pure decision function: should intra-compaction trigger now?
|
||||
///
|
||||
/// Returns `Some(trigger)` if all gating conditions are met; `None` otherwise.
|
||||
/// Caller must additionally check the feature flag and/or any global kill
|
||||
/// switch — this function deals only with the policy + step state.
|
||||
///
|
||||
/// `min_steps_before_compact` remains on [`IntraCompactionConfig`] for every
|
||||
/// mode, but is **not** enforced when
|
||||
/// [`mode`](IntraCompactionConfig::mode) is
|
||||
/// [`IntraCompactionMode::FullReplace`] — that path matches grok-build's
|
||||
/// full-replace trigger (token threshold alone) so a large first-step prompt
|
||||
/// can still compact. Partial modes still gate on min steps.
|
||||
pub fn should_compact(
|
||||
policy: &IntraCompactionConfig,
|
||||
last_prompt_tokens: u32,
|
||||
context_window: u32,
|
||||
current_step: u32,
|
||||
) -> Option<IntraCompactionTrigger> {
|
||||
if !policy.enabled {
|
||||
return None;
|
||||
}
|
||||
if context_window == 0 {
|
||||
return None;
|
||||
}
|
||||
// FullReplace: token threshold only (field still present on config).
|
||||
// Partial modes: skip early steps with little content to reduce.
|
||||
if policy.mode != IntraCompactionMode::FullReplace
|
||||
&& current_step < policy.min_steps_before_compact
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let threshold = (context_window as u64 * policy.trigger_threshold_percent as u64 / 100) as u32;
|
||||
if last_prompt_tokens <= threshold {
|
||||
return None;
|
||||
}
|
||||
|
||||
let percent = (last_prompt_tokens as u64 * 100 / context_window as u64).min(100) as u8;
|
||||
Some(IntraCompactionTrigger {
|
||||
last_prompt_tokens,
|
||||
context_window,
|
||||
percent,
|
||||
step: current_step,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn enabled_policy() -> IntraCompactionConfig {
|
||||
IntraCompactionConfig {
|
||||
enabled: true,
|
||||
// Default mode is FullReplace — min_steps stored but not enforced.
|
||||
trigger_threshold_percent: 85,
|
||||
target_threshold_percent: 50,
|
||||
min_steps_before_compact: 3,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn enabled_partial_policy(mode: IntraCompactionMode) -> IntraCompactionConfig {
|
||||
IntraCompactionConfig {
|
||||
mode,
|
||||
..enabled_policy()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_when_disabled() {
|
||||
let mut p = enabled_policy();
|
||||
p.enabled = false;
|
||||
assert!(should_compact(&p, 90_000, 100_000, 10).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_when_below_threshold() {
|
||||
let p = enabled_policy();
|
||||
// 84% of 100K = 84_000, threshold 85% = 85_000.
|
||||
assert!(should_compact(&p, 84_000, 100_000, 10).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_some_when_above_threshold() {
|
||||
let p = enabled_policy();
|
||||
let t = should_compact(&p, 90_000, 100_000, 10).expect("should trigger");
|
||||
assert_eq!(t.last_prompt_tokens, 90_000);
|
||||
assert_eq!(t.context_window, 100_000);
|
||||
assert_eq!(t.percent, 90);
|
||||
assert_eq!(t.step, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_replace_keeps_field_but_ignores_min_steps() {
|
||||
let p = enabled_policy();
|
||||
assert_eq!(p.mode, IntraCompactionMode::FullReplace);
|
||||
assert_eq!(p.min_steps_before_compact, 3);
|
||||
// Field is present; FullReplace only uses the token threshold
|
||||
// (parity with grok-build auto-compact).
|
||||
let t = should_compact(&p, 90_000, 100_000, 0).expect("should trigger");
|
||||
assert_eq!(t.step, 0);
|
||||
assert!(should_compact(&p, 90_000, 100_000, 2).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_modes_enforce_min_steps() {
|
||||
for mode in [
|
||||
IntraCompactionMode::StepsOnly,
|
||||
IntraCompactionMode::HistoryOnly,
|
||||
IntraCompactionMode::HistoryThenSteps,
|
||||
] {
|
||||
let p = enabled_partial_policy(mode);
|
||||
assert!(
|
||||
should_compact(&p, 90_000, 100_000, 2).is_none(),
|
||||
"{mode:?} should gate on min_steps"
|
||||
);
|
||||
let t = should_compact(&p, 90_000, 100_000, 3).expect("should trigger at min steps");
|
||||
assert_eq!(t.step, 3);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_when_context_window_zero() {
|
||||
let p = enabled_policy();
|
||||
assert!(should_compact(&p, 1_000, 0, 10).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn percent_caps_at_100() {
|
||||
let p = enabled_policy();
|
||||
let t = should_compact(&p, 200_000, 100_000, 10).expect("should trigger");
|
||||
assert_eq!(t.percent, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boundary_exact_threshold_does_not_trigger() {
|
||||
let p = enabled_policy();
|
||||
// last_prompt_tokens == threshold: not strictly greater than.
|
||||
assert!(should_compact(&p, 85_000, 100_000, 10).is_none());
|
||||
// One above triggers.
|
||||
assert!(should_compact(&p, 85_001, 100_000, 10).is_some());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user