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
@@ -0,0 +1,286 @@
//! Shared image re-encoding with PNG+JPEG format selection.
//!
//! Both the user-attachment normalizer (`kigi-shell`) and the `read_file`
//! tool image path use this to compress images under a byte-size cap while
//! respecting per-caller dimension and quality parameters.
use std::borrow::Cow;
use image::DynamicImage;
use image::codecs::jpeg::JpegEncoder;
pub use image::imageops::FilterType;
/// Parameters that control the re-encode loop.
///
/// Each call-site provides its own set of limits so the shared encoder can
/// serve callers with different size/quality trade-offs.
#[derive(Debug, Clone, Copy)]
pub struct ReEncodeParams {
/// Maximum output size in **raw bytes** (not base64).
pub max_bytes: usize,
/// Maximum dimension (width or height) on the first attempt.
pub max_side_px: u32,
/// Maximum total output pixel count (width × height) on the first
/// attempt; `u64::MAX` disables the area cap.
pub max_pixels: u64,
/// Floor dimension — the loop gives up when `max_side` falls to or below
/// this value without producing output that fits.
pub min_side_px: u32,
/// JPEG quality steps to try at each dimension, in descending order.
pub quality_steps: &'static [u8],
/// Resize filter (e.g. `CatmullRom`, `Lanczos3`).
pub filter: FilterType,
}
impl ReEncodeParams {
/// True when either side exceeds `max_side_px` or the total pixel count
/// exceeds `max_pixels` — shared by re-encode triggers and passthrough
/// gates so the rule cannot drift between them.
pub fn exceeds_dimension_caps(&self, w: u32, h: u32) -> bool {
w > self.max_side_px
|| h > self.max_side_px
|| u64::from(w) * u64::from(h) > self.max_pixels
}
}
/// Why `re_encode_under_limit` could not produce a compliant output.
#[derive(Debug, thiserror::Error)]
pub enum ReEncodeError {
/// Exhausted all quality × dimension steps without fitting under the cap.
#[error(
"re-encode could not fit under {max_bytes} bytes after PNG+JPEG attempts (last side {last_side}px)"
)]
CouldNotFit { max_bytes: usize, last_side: u32 },
}
/// Try PNG and JPEG encodings at descending dimensions, returning whichever
/// is smallest and fits under `params.max_bytes`.
///
/// On success returns `(bytes, width, height, mime_type)`.
pub fn re_encode_under_limit(
decoded: &DynamicImage,
params: &ReEncodeParams,
) -> Result<(Vec<u8>, u32, u32, &'static str), ReEncodeError> {
// Never upscale: a small-but-heavy image is re-encoded at its own
// resolution, not enlarged to `max_side_px`. `image::resize` scales *up* to
// fill the target box, so starting at `max_side_px` would enlarge anything
// smaller — adding no detail and wasting request bytes / cache headroom.
let original_max_side = decoded.width().max(decoded.height());
let mut max_side = params.max_side_px.min(original_max_side);
let original_pixels = u64::from(decoded.width()) * u64::from(decoded.height());
if original_pixels > params.max_pixels {
max_side = max_side.min(area_capped_side(
original_max_side,
decoded.width().min(decoded.height()),
params.max_pixels,
));
}
loop {
// Only resample when actually downscaling; resizing to the current size
// would just soften the image for no reason. `resize(w, h)` preserves
// aspect ratio (fits inside w×h, not stretch-to-square).
let scaled: Cow<'_, DynamicImage> = if max_side < original_max_side {
Cow::Owned(decoded.resize(max_side, max_side, params.filter))
} else {
Cow::Borrowed(decoded)
};
let img: &DynamicImage = &scaled;
let (w, h) = (img.width(), img.height());
// --- PNG candidate ---------------------------------------------------
let png_candidate = {
let mut buf = Vec::new();
img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
.ok()
.filter(|_| buf.len() <= params.max_bytes)
.map(|_| buf)
};
// --- JPEG candidate (best quality that fits) -------------------------
let jpeg_candidate = params.quality_steps.iter().find_map(|&quality| {
let mut buf = Vec::new();
let mut enc = JpegEncoder::new_with_quality(&mut buf, quality);
enc.encode_image(img).ok()?;
(buf.len() <= params.max_bytes).then_some(buf)
});
// --- Pick the smaller candidate --------------------------------------
match (png_candidate, jpeg_candidate) {
(Some(png), Some(jpeg)) => {
if png.len() <= jpeg.len() {
return Ok((png, w, h, "image/png"));
} else {
return Ok((jpeg, w, h, "image/jpeg"));
}
}
(Some(png), None) => return Ok((png, w, h, "image/png")),
(None, Some(jpeg)) => return Ok((jpeg, w, h, "image/jpeg")),
(None, None) => { /* fall through to smaller dimensions */ }
}
if max_side <= params.min_side_px {
return Err(ReEncodeError::CouldNotFit {
max_bytes: params.max_bytes,
last_side: max_side,
});
}
max_side = max_side * 3 / 4;
}
}
/// Largest target long side whose resize output area stays within `max_pixels`.
fn area_capped_side(long: u32, short: u32, max_pixels: u64) -> u32 {
let scale = (max_pixels as f64 / (u64::from(long) * u64::from(short)) as f64).sqrt();
let mut side = ((f64::from(long) * scale).floor() as u32).clamp(1, long);
// Nearest-rounding of the short side can overshoot the budget by ~side/2
// pixels, so step down until the predicted output fits: a decrement removes
// ~2*area/side pixels, giving ~2 iterations for ordinary aspect ratios;
// only degenerate strips whose short side pins at the 1px floor walk
// O(side), bounded by the callers' decode-pixel limits.
while side > 1 && predicted_resize_area(long, short, side) > max_pixels {
side -= 1;
}
side
}
/// Output area `image::resize` produces for a `side`×`side` bounding box,
/// mirroring `resize_dimensions` (image-0.25.9, `src/math/utils.rs`)
/// expression-for-expression; the `area_cap_exact_fit_across_aspect_ratios`
/// sweep pins the equivalence through the real resize, so a crate bump that
/// changes the rounding shows up as a test failure pointing here.
fn predicted_resize_area(long: u32, short: u32, side: u32) -> u64 {
let ratio = f64::from(side) / f64::from(long);
let scaled_long = (f64::from(long) * ratio).round().max(1.0) as u64;
let scaled_short = (f64::from(short) * ratio).round().max(1.0) as u64;
scaled_long * scaled_short
}
#[cfg(test)]
mod tests {
use image::{DynamicImage, Rgb, RgbImage};
use super::*;
/// Deterministic high-entropy image so PNG/JPEG can't trivially shrink it.
fn noise(w: u32, h: u32) -> DynamicImage {
let mut img = RgbImage::new(w, h);
let mut s: u32 = 0x1234_5678;
for p in img.pixels_mut() {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
*p = Rgb([(s >> 16) as u8, (s >> 8) as u8, s as u8]);
}
DynamicImage::ImageRgb8(img)
}
fn params(max_bytes: usize, max_side_px: u32, max_pixels: u64) -> ReEncodeParams {
ReEncodeParams {
max_bytes,
max_side_px,
max_pixels,
min_side_px: 256,
quality_steps: &[88, 72, 56, 40, 24],
filter: FilterType::CatmullRom,
}
}
#[test]
fn does_not_upscale_images_smaller_than_the_side_cap() {
// 1280x960 is already under the test's 1568px side cap. Re-encoding
// must NOT enlarge it — output dimensions must never exceed the input.
// (Regression: the resize previously scaled small images up to
// `max_side_px`.)
let img = noise(1280, 960);
let (_bytes, w, h, _mime) =
re_encode_under_limit(&img, &params(5_000_000, 1568, u64::MAX)).unwrap();
assert!(
w <= 1280 && h <= 960,
"must not upscale a 1280x960 image, got {w}x{h}"
);
}
#[test]
fn downscales_images_larger_than_the_side_cap() {
// 2000x1500 exceeds the test's 1568px side cap and must fit inside it.
let img = noise(2000, 1500);
let (_bytes, w, h, _mime) =
re_encode_under_limit(&img, &params(5_000_000, 1568, u64::MAX)).unwrap();
assert!(
w <= 1568 && h <= 1568,
"should downscale to <=1568, got {w}x{h}"
);
assert_eq!(w, 1568, "longest side should hit the cap");
}
#[test]
fn shrinks_dimensions_only_when_bytes_force_it() {
// A small image that can't fit the byte cap at native size is
// downscaled below its own dimensions — still never above them.
let img = noise(1280, 960);
let (bytes, w, h, _mime) =
re_encode_under_limit(&img, &params(120_000, 1568, u64::MAX)).unwrap();
assert!(bytes.len() <= 120_000);
assert!(w <= 1280 && h <= 960, "must not upscale, got {w}x{h}");
}
#[test]
fn area_cap_bounds_total_pixels_for_wide_images() {
// 3438x1830 = 6.29 Mpx: the side cap is loose, so only the area budget
// binds; expected long side = floor(3438 * sqrt(2_408_448 / 6_291_540)).
let img = noise(3438, 1830);
let (_bytes, w, h, _mime) =
re_encode_under_limit(&img, &params(50_000_000, 10_000, 2_408_448)).unwrap();
let area = u64::from(w) * u64::from(h);
assert!(area <= 2_408_448, "area {area} over budget ({w}x{h})");
assert!(
area >= 2_300_000,
"should use most of the budget, got {area} ({w}x{h})"
);
assert_eq!(w, 2127, "long side ~2127 for a 3438x1830 source");
let r_in = 3438.0 / 1830.0;
let r_out = w as f64 / h as f64;
assert!(
(r_in - r_out).abs() < 0.01,
"aspect ratio {r_in} -> {r_out} ({w}x{h})"
);
}
#[test]
fn image_under_area_cap_is_not_resized() {
// 1500x1500 = 2.25 Mpx is under the 2_408_448 budget; no resample.
let img = noise(1500, 1500);
let (_bytes, w, h, _mime) =
re_encode_under_limit(&img, &params(50_000_000, 10_000, 2_408_448)).unwrap();
assert_eq!((w, h), (1500, 1500), "must not up- or downscale");
}
#[test]
fn area_cap_exact_fit_across_aspect_ratios() {
// Short-side rounding must never push the output area over the cap;
// (1600, 400, 300_000) rounds 273.5 up and exercises the decrement.
for &(sw, sh, cap) in &[
(1300u32, 900u32, 500_000u64),
(1200, 1199, 640_000),
(1600, 400, 300_000),
(900, 1300, 777_777),
(1000, 1000, 123_456),
(2600, 1800, 2_408_448),
] {
let img = noise(sw, sh);
let (_bytes, w, h, _mime) =
re_encode_under_limit(&img, &params(50_000_000, 10_000, cap)).unwrap();
let area = u64::from(w) * u64::from(h);
assert!(area <= cap, "{sw}x{sh} cap {cap}: got {w}x{h} = {area}");
assert_eq!(
w.max(h),
area_capped_side(sw.max(sh), sw.min(sh), cap),
"{sw}x{sh} cap {cap}: long side must match the predicted fit"
);
}
}
}