Files
Kigi-CLI/crates/common/kigi-tool-protocol/src/methods.rs
T
ZacharyZhang-NY d6c20fc13f 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).
2026-07-17 05:31:01 -04:00

209 lines
7.6 KiB
Rust

//! Closed enumeration of every JSON-RPC method on the wire.
//!
//! Each variant is defined once in the [`define_methods!`] macro invocation
//! together with its wire string. The macro generates the enum, serde
//! renames, [`Method::as_wire_str`], and [`Method::from_wire_str`] from
//! that single source of truth.
use serde::{Deserialize, Serialize};
macro_rules! define_methods {
(
$(
$(#[$var_attr:meta])*
$variant:ident => $wire:literal
),* $(,)?
) => {
/// Every JSON-RPC method understood by the computer hub.
///
/// The variants are grouped by direction in source order; the enum is
/// flat — direction enforcement is the computer hub's job, not the
/// protocol crate's.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Method {
$(
$(#[$var_attr])*
#[serde(rename = $wire)]
$variant,
)*
}
impl Method {
/// Every `Method` variant, for exhaustive iteration in tests.
pub const ALL: &'static [Method] = &[$(Self::$variant,)*];
/// Wire string for this method. Equivalent to the serde
/// serialization but without a round-trip through `serde_json`.
pub const fn as_wire_str(self) -> &'static str {
match self {
$(Self::$variant => $wire,)*
}
}
/// Inverse of [`Self::as_wire_str`]. Returns `None` for
/// strings that don't match any known method.
pub fn from_wire_str(s: &str) -> Option<Self> {
match s {
$($wire => Some(Self::$variant),)*
_ => None,
}
}
}
};
}
/// Message prefix the hub uses when rejecting a request whose `method`
/// string does not parse into [`Method`] — the shape an OLD hub produces
/// for verbs it predates. Current clients answer hub skew from the
/// `hello_ack` `capabilities` advertisement instead of sniffing this
/// message, but the shape stays pinned here: terminal binaries built
/// while the SDK still keyed old-hub detection on this exact prefix
/// remain in the fleet. Do not change casually.
pub const UNKNOWN_METHOD_MSG_PREFIX: &str = "unknown method `";
define_methods! {
// harness → service
SessionOpen => "session_open",
SessionClose => "session_close",
SessionBindServer => "session_bind_server",
SessionUnbindServer => "session_unbind_server",
/// Attach this harness connection to an EXISTING session as an
/// observer. Answered hub-locally from the session→tool-server
/// routing established by the owner's `session_bind_server` (or the
/// server's re-`serve`); never forwarded to the tool server.
SessionAttachServer => "session_attach_server",
ToolsList => "tools.list",
ToolsSearch => "tools.search",
ToolCall => "tool.call",
/// Sugar for [`Method::Hook`] with [`crate::HookEvent::Cancel`].
/// SDKs translate this method to a hook frame before sending; there
/// is no separate `tool.cancel` wire frame and no `ToolCancelParams`
/// struct in [`crate::frames`].
ToolCancel => "tool.cancel",
ToolNotify => "tool.notify",
SystemNotify => "system.notify",
SubscribeNotifications => "subscribe_notifications",
UnsubscribeNotifications => "unsubscribe_notifications",
Hook => "hook",
Hello => "hello",
HelloAck => "hello_ack",
Ping => "ping",
Pong => "pong",
// tool_server → service
ToolCallProgress => "tool_call_progress",
ToolNotification => "tool.notification",
/// Reply to a request/response hook, correlated back to the harness by `hook_id`.
HookReply => "hook_reply",
/// Notification (no `id`, no response); rejects surface only in
/// hub metrics. Only hub-minted trace-ids are accepted.
TracesDonate => "traces.donate",
/// Notification (no `id`, no response); rejects surface only in hub
/// metrics. Donor service.name must be hub-allowlisted.
LogsDonate => "logs.donate",
/// Notification (no `id`, no response); rejects surface only in hub
/// metrics. Donor service.name must be hub-allowlisted. No envelope
/// `session_id` — metrics are process-aggregate.
MetricsDonate => "metrics.donate",
// service → tool_server
ToolCallRequest => "tool_call_request",
// service → harness
ToolsChanged => "tools_changed",
SubscribeAck => "subscribe_ack",
UnsubscribeAck => "unsubscribe_ack",
// harness → service (server discovery)
/// List available tool servers for the authenticated user.
ServersList => "servers.list",
// tool_server status lifecycle
ToolServerStatus => "tool_server.status",
ToolServerGetStatus => "tool_server.get_status",
ToolServerEvict => "tool_server.evict",
// ── Session lifecycle ───────────────────────────────────────────
/// Full tool snapshot for a session (server → hub). Idempotent:
/// re-sending replaces the tool set; the hub diffs and emits
/// `tools_changed`.
Serve => "serve",
/// Hub requests the server to start serving a session
/// (hub → server). The server responds with its tool snapshot.
SessionBind => "session.bind",
/// Hub tells the server to stop serving a session
/// (hub → server). Notification — no response expected.
SessionUnbind => "session.unbind",
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_as_wire_str_from_wire_str() {
let all = [
Method::SessionOpen,
Method::SessionClose,
Method::SessionBindServer,
Method::SessionUnbindServer,
Method::SessionAttachServer,
Method::ToolsList,
Method::ToolsSearch,
Method::ToolCall,
Method::ToolCancel,
Method::ToolNotify,
Method::SystemNotify,
Method::SubscribeNotifications,
Method::UnsubscribeNotifications,
Method::Hook,
Method::Hello,
Method::HelloAck,
Method::Ping,
Method::Pong,
Method::ToolCallProgress,
Method::ToolNotification,
Method::HookReply,
Method::TracesDonate,
Method::LogsDonate,
Method::MetricsDonate,
Method::ToolCallRequest,
Method::ToolsChanged,
Method::SubscribeAck,
Method::UnsubscribeAck,
Method::ServersList,
Method::ToolServerStatus,
Method::ToolServerGetStatus,
Method::ToolServerEvict,
Method::Serve,
Method::SessionBind,
Method::SessionUnbind,
];
for m in all {
assert_eq!(Method::from_wire_str(m.as_wire_str()), Some(m));
}
}
#[test]
fn from_wire_str_returns_none_for_unknown() {
assert_eq!(Method::from_wire_str("not_a_method"), None);
assert_eq!(Method::from_wire_str(""), None);
}
#[test]
fn serde_round_trip_matches_wire_str() {
let method = Method::ToolCall;
let json = serde_json::to_value(method).expect("serialize");
assert_eq!(json.as_str(), Some("tool.call"));
let back: Method = serde_json::from_value(json).expect("deserialize");
assert_eq!(back, method);
}
}