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:
ZacharyZhang-NY committed 2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 files changed
+1353757

No files matched your search

+246
View File
@@ -0,0 +1,246 @@
//! Sampler configuration types.
//!
//! [`SamplerConfig`] is the per-request configuration handed to the
//! sampler. It deliberately does **not** alias
//! `kigi_sampling_types::SamplingConfig` so that the sampler crate
//! avoids transitive dependencies on shell-specific types
//! (`kigi-tools`, etc.).
use indexmap::IndexMap;
use kigi_sampling_types::{
ApiBackend, CompactionAtTokens, CompactionsRemaining, DoomLoopRecoveryPolicy, ReasoningEffort,
};
use serde::{Deserialize, Serialize};
use crate::attribution::SharedAttributionCallback;
use crate::retry::{DEFAULT_MAX_RETRIES, RATE_LIMIT_RETRY_THRESHOLD};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum AuthScheme {
#[default]
Bearer,
XApiKey,
}
/// All knobs that control a single sampling request.
///
/// The session typically owns one `SamplerConfig` per active model
/// and passes it (or a per-request override) to the actor on every
/// submit.
///
/// # Construction in `kigi-shell`
///
/// `SamplerConfig` is the single source of truth for sampler
/// configuration. The shell builds it directly (see
/// `agent::config::resolve_model_to_sampling_config` and
/// `session::acp_session::SessionActor::reconstruct_full_config`) by
/// composing chat-state's `kigi_sampling_types::SamplingConfig`
/// with `Credentials` (api key, client version).
///
/// URL-derived request headers (e.g. `X-XAI-Token-Auth` for the
/// cli-chat-proxy) are
/// folded into [`Self::extra_headers`] by
/// `agent::config::inject_url_derived_headers` before the
/// `SamplerConfig` is handed to the actor. Auth is selected separately
/// via `auth_scheme`, while `api_backend` controls only the request/response
/// protocol shape.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SamplerConfig {
pub api_key: Option<String>,
pub base_url: String,
pub model: String,
pub max_completion_tokens: Option<u32>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub api_backend: ApiBackend,
#[serde(default)]
pub auth_scheme: AuthScheme,
/// Extra request headers applied verbatim. The sampler never inspects
/// the URL to derive headers; callers (the session) inject proxy auth
/// and other access headers here before constructing the config.
pub extra_headers: IndexMap<String, String>,
/// Total context window size in tokens. The sampler does not enforce
/// it; it is informational metadata used by the session for compaction
/// decisions.
pub context_window: u64,
pub force_http1: bool,
pub max_retries: Option<u32>,
pub stream_tool_calls: bool,
pub idle_timeout_secs: Option<u64>,
// Reasoning effort
pub reasoning_effort: Option<ReasoningEffort>,
// Client identity
pub origin_client: Option<OriginClientInfo>,
pub client_identifier: Option<String>,
pub deployment_id: Option<String>,
pub user_id: Option<String>,
pub client_version: Option<String>,
/// Optional hook invoked at every UNAUTHORIZED (401) response
/// site. The sampler passes the bearer that was actually sent on
/// the wire to the callback; the implementation is free to do
/// whatever it wants with it (typically: join it with a live
/// credential source and emit an attribution event for diagnosis
/// of stale-token vs. server-rejected-live-token 401s). `None`
/// (default) is a no-op -- the 401 arm returns the same
/// `SamplingError::Auth` it always did.
///
/// `Arc<dyn Trait>` is not serializable, so the field is skipped
/// in (de)serialization. Round-tripping a config through serde
/// drops the callback; callers that deserialize a `SamplerConfig`
/// from disk must re-attach the callback before passing it to
/// [`crate::SamplingClient::new`] or 401 attribution will be
/// silently disabled for the rebuilt client.
#[serde(skip)]
pub attribution_callback: Option<SharedAttributionCallback>,
/// Live bearer resolve per request. `None` uses construction-time `api_key`.
#[serde(skip)]
pub bearer_resolver: Option<SharedBearerResolver>,
#[serde(default)]
pub supports_backend_search: bool,
/// Per-model config for the `x-compactions-remaining` header; `None` disables it.
#[serde(default)]
pub compactions_remaining: Option<CompactionsRemaining>,
/// Per-model config for the `x-compaction-at` header; `None` disables it.
#[serde(default)]
pub compaction_at_tokens: Option<CompactionAtTokens>,
/// Server-side doom-loop check policy; `None` disables it. When set, the
/// client itself sends the opt-in `x-grok-doom-loop-check` header on
/// streaming Responses API requests and absorbs the reported trigger
/// events (unlike the environment headers in [`Self::extra_headers`],
/// this header gates the client's own decode behavior, so it lives with
/// the decoder).
#[serde(default)]
pub doom_loop_recovery: Option<DoomLoopRecoveryPolicy>,
/// Per-request header injector (e.g. OTel traceparent). Called in `post()`.
#[serde(skip)]
pub header_injector: Option<SharedHeaderInjector>,
}
impl Default for SamplerConfig {
/// Empty defaults so callers can use `..Default::default()` and
/// new fields don't ripple through every literal site.
fn default() -> Self {
Self {
api_key: None,
base_url: String::new(),
model: String::new(),
max_completion_tokens: None,
temperature: None,
top_p: None,
api_backend: ApiBackend::default(),
auth_scheme: AuthScheme::default(),
extra_headers: IndexMap::new(),
context_window: 0,
force_http1: false,
max_retries: None,
stream_tool_calls: false,
idle_timeout_secs: None,
reasoning_effort: None,
origin_client: None,
client_identifier: None,
deployment_id: None,
user_id: None,
client_version: None,
attribution_callback: None,
bearer_resolver: None,
supports_backend_search: false,
compactions_remaining: None,
compaction_at_tokens: None,
doom_loop_recovery: None,
header_injector: None,
}
}
}
/// Cheap sync read of the current bearer for [`SamplerConfig::bearer_resolver`].
pub trait BearerResolver: Send + Sync + std::fmt::Debug {
fn current_bearer(&self) -> Option<String>;
}
pub type SharedBearerResolver = std::sync::Arc<dyn BearerResolver>;
/// Per-request header injection (e.g. OTel `traceparent`).
pub trait HeaderInjector: Send + Sync + std::fmt::Debug {
fn inject(&self, headers: &mut reqwest::header::HeaderMap);
}
pub type SharedHeaderInjector = std::sync::Arc<dyn HeaderInjector>;
/// Retry knobs for the sampler's internal transport-error retry loop.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryPolicy {
/// Maximum number of retries before giving up.
pub max_retries: u32,
/// After this many rate-limit (429) retries, escalate to the caller.
/// Lower than `max_retries` because rate-limit waits can be long.
pub rate_limit_retry_threshold: u32,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_retries: DEFAULT_MAX_RETRIES,
rate_limit_retry_threshold: RATE_LIMIT_RETRY_THRESHOLD,
}
}
}
/// Identity of the client that originated the request, used for
/// User-Agent rendering. The shell layer composes this with platform
/// info into a final UA string.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct OriginClientInfo {
pub product: String,
pub version: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retry_policy_defaults() {
let policy = RetryPolicy::default();
assert_eq!(policy.max_retries, DEFAULT_MAX_RETRIES);
assert_eq!(
policy.rate_limit_retry_threshold,
RATE_LIMIT_RETRY_THRESHOLD
);
}
/// Configs serialized before the field existed must keep deserializing.
#[test]
fn config_without_doom_loop_recovery_deserializes_to_none() {
let mut stripped = serde_json::to_value(SamplerConfig::default()).unwrap();
stripped
.as_object_mut()
.unwrap()
.remove("doom_loop_recovery");
let config: SamplerConfig = serde_json::from_value(stripped).unwrap();
assert!(config.doom_loop_recovery.is_none());
let with_policy = SamplerConfig {
doom_loop_recovery: Some(DoomLoopRecoveryPolicy {
max_threshold: 8,
max_retries: 2,
}),
..Default::default()
};
let round_tripped: SamplerConfig =
serde_json::from_value(serde_json::to_value(&with_policy).unwrap()).unwrap();
assert_eq!(
round_tripped.doom_loop_recovery,
with_policy.doom_loop_recovery
);
}
}