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,115 @@
|
||||
use agent_client_protocol as acp;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use crate::message::{AcpAgentMessage, AcpClientMessage};
|
||||
|
||||
pub type AcpResult<T> = Result<T, acp::Error>;
|
||||
|
||||
pub type AcpRxo<T> = oneshot::Receiver<AcpResult<T>>;
|
||||
pub type AcpTxo<T> = oneshot::Sender<AcpResult<T>>;
|
||||
|
||||
pub type AcpClientRx = mpsc::UnboundedReceiver<AcpClientMessage>;
|
||||
pub type AcpClientTx = mpsc::UnboundedSender<AcpClientMessage>;
|
||||
|
||||
pub type AcpAgentRx = mpsc::UnboundedReceiver<AcpAgentMessage>;
|
||||
pub type AcpAgentTx = mpsc::UnboundedSender<AcpAgentMessage>;
|
||||
|
||||
pub fn acp_internal_error(message: impl Into<String>) -> acp::Error {
|
||||
acp::Error::new(acp::ErrorCode::InternalError.into(), message)
|
||||
}
|
||||
|
||||
/// The two distinct ways an [`acp_send`](crate::acp_send) round-trip can fail
|
||||
/// when the underlying channel is closed. Both surface as a JSON-RPC
|
||||
/// `INTERNAL_ERROR` (so existing callers and the wire format are unaffected);
|
||||
/// this typed discriminant — carried in the error's `data` — lets callers tell
|
||||
/// them apart WITHOUT substring-matching the human-readable `message`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AcpChannelFailure {
|
||||
/// The request could not be ENQUEUED: the receiver half (the peer's
|
||||
/// connection task) is already gone, so no peer is listening — e.g. a
|
||||
/// headless run with no client wired.
|
||||
SendFailed,
|
||||
/// The request was enqueued but the RESPONSE channel was dropped before a
|
||||
/// reply arrived: a peer received the request, then went away (disconnect /
|
||||
/// process exit) without answering.
|
||||
RecvFailed,
|
||||
}
|
||||
|
||||
impl AcpChannelFailure {
|
||||
/// `data` object key under which [`acp_send`](crate::acp_send) records the
|
||||
/// kind. Namespaced so it can never collide with other `with_data` payloads.
|
||||
const DATA_KEY: &'static str = "xaiAcpChannelFailure";
|
||||
|
||||
const fn tag(self) -> &'static str {
|
||||
match self {
|
||||
Self::SendFailed => "send_failed",
|
||||
Self::RecvFailed => "recv_failed",
|
||||
}
|
||||
}
|
||||
|
||||
fn from_tag(tag: &str) -> Option<Self> {
|
||||
match tag {
|
||||
"send_failed" => Some(Self::SendFailed),
|
||||
"recv_failed" => Some(Self::RecvFailed),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the channel-closed error for [`acp_send`](crate::acp_send), tagging it
|
||||
/// with a typed [`AcpChannelFailure`] discriminant in `data`. The error `code`
|
||||
/// stays `INTERNAL_ERROR`, so this is purely additive for callers that just
|
||||
/// propagate the error.
|
||||
pub(crate) fn acp_channel_failure_error(
|
||||
message: impl Into<String>,
|
||||
kind: AcpChannelFailure,
|
||||
) -> acp::Error {
|
||||
acp_internal_error(message).data(serde_json::json!({ AcpChannelFailure::DATA_KEY: kind.tag() }))
|
||||
}
|
||||
|
||||
/// Recover the [`AcpChannelFailure`] kind from an error, or `None` if the error
|
||||
/// did not originate from [`acp_send`](crate::acp_send)'s channel-closed paths
|
||||
/// (or predates the tag). Consumers use this instead of inspecting `message`.
|
||||
pub fn acp_channel_failure(err: &acp::Error) -> Option<AcpChannelFailure> {
|
||||
err.data
|
||||
.as_ref()
|
||||
.and_then(|data| data.get(AcpChannelFailure::DATA_KEY))
|
||||
.and_then(|value| value.as_str())
|
||||
.and_then(AcpChannelFailure::from_tag)
|
||||
}
|
||||
|
||||
/// Compact single-line JSON for gateway debug traces. Plain (uncolored)
|
||||
/// output: this feeds `tracing::debug!`, which typically lands in log files
|
||||
/// where ANSI colors are noise. Replaces the former `colored_json`-backed
|
||||
/// `color_json` (dropped to shrink the shipped dependency tree).
|
||||
#[doc(hidden)]
|
||||
pub fn compact_json<T: serde::Serialize>(value: &T) -> String {
|
||||
serde_json::to_string(value).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod channel_failure_tests {
|
||||
use super::{
|
||||
AcpChannelFailure, acp, acp_channel_failure, acp_channel_failure_error, acp_internal_error,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn classifier_round_trips_both_kinds() {
|
||||
for kind in [AcpChannelFailure::SendFailed, AcpChannelFailure::RecvFailed] {
|
||||
let err = acp_channel_failure_error("boom", kind);
|
||||
// Code stays INTERNAL_ERROR for backward compatibility.
|
||||
assert_eq!(err.code, acp::ErrorCode::InternalError);
|
||||
assert_eq!(acp_channel_failure(&err), Some(kind));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifier_none_for_untagged_errors() {
|
||||
assert_eq!(acp_channel_failure(&acp_internal_error("plain")), None);
|
||||
// A different `with_data` payload must not be misread as a channel kind.
|
||||
assert_eq!(
|
||||
acp_channel_failure(&acp::Error::invalid_params().data("unknown session id")),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user