Files
Kigi-CLI/crates/common/kigi-tool-protocol/src/registration.rs
T
ZacharyZhang-NY a02b555e66 docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
2026-07-23 16:55:39 -04:00

153 lines
6.3 KiB
Rust

//! Registration payloads, descriptions, transports, and outcomes.
use serde::{Deserialize, Serialize};
use crate::{
HookKind, IdError, NotificationSchemas, ServerId, SessionId, ToolCapabilities, ToolId, UserId,
};
/// Whether a registered tool runs in-process or behind a remote connection.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TransportKind {
Local,
Remote,
}
/// The `tool_id` is **not** carried explicitly — it is derived from
/// `description.{namespace, name}` via [`Self::derive_tool_id`].
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolDescriptionWithSchema {
pub description: kigi_tool_types::ToolDescription,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_schema: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capabilities: Option<ToolCapabilities>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notification_schemas: Option<NotificationSchemas>,
}
impl ToolDescriptionWithSchema {
/// Namespaced descriptions render as `"{namespace}:{name}"`; otherwise
/// the bare `name`. The result is run through [`ToolId::new`], so an
/// invalid name or namespace surfaces as an [`IdError`].
pub fn derive_tool_id(&self) -> Result<ToolId, IdError> {
match &self.description.namespace {
Some(ns) => ToolId::new(format!("{ns}:{}", self.description.name)),
None => ToolId::new(self.description.name.as_str()),
}
}
}
/// Single-tool registration. Wire-level sugar for a one-tool
/// `register_server`.
///
/// `sessions` carries the per-tool session set with three-state
/// semantics, modelled after `if_match_generation: Option<u64>`:
///
/// - `None` (field omitted on the wire) — "no change". For a
/// first-time registration the computer hub treats this as the empty
/// set; for a re-registration the existing session bindings are
/// preserved untouched. Use this for heartbeat-style re-register
/// flows that re-send the description without revisiting the
/// session set.
/// - `Some(vec![])` (explicit empty array) — "unbind every session".
/// The tool stays registered against the connection but becomes
/// unreachable from any session until [`crate::Method::BindToolSession`]
/// adds a new binding.
/// - `Some(vec![s1, ...])` — replace the per-tool session set with
/// exactly the listed ids. Each id MUST already be in the
/// connection's bound-session set (validated by the router);
/// a missing id rejects the entire registration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolRegistration {
/// MUST equal `description.derive_tool_id()`. Carried explicitly so
/// receivers can route without re-deriving. The IC service router
/// enforces this at register-tool time and rejects mismatches with
/// `InvalidRequest`.
pub tool_id: ToolId,
/// See the struct doc-comment for the three-state semantics.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sessions: Option<Vec<SessionId>>,
pub user_id: UserId,
/// `None` → computer hub synthesises `auto:tool:{tool_id}`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub server_id: Option<ServerId>,
pub description: kigi_tool_types::ToolDescription,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_schema: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capabilities: Option<ToolCapabilities>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notification_schemas: Option<NotificationSchemas>,
pub transport_kind: TransportKind,
/// Optimistic-concurrency precondition. `None` → last-writer-wins.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub if_match_generation: Option<u64>,
/// Opaque metadata supplied by the tool server at registration time.
/// Propagated to `ServerInfo.metadata` in `servers.list` responses.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
impl ToolRegistration {
pub fn derive_tool_id(&self) -> Result<ToolId, IdError> {
match &self.description.namespace {
Some(ns) => ToolId::new(format!("{ns}:{}", self.description.name)),
None => ToolId::new(self.description.name.as_str()),
}
}
}
/// Multi-tool registration. The whole batch shares one `server_id` and one
/// `sessions` value; per-tool outcomes are reported individually via
/// [`RegistrationOutcome`].
///
/// `sessions` follows the three-state semantics documented on
/// [`ToolRegistration`], applied to every tool in the batch.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolServerRegistration {
pub server_id: ServerId,
/// See the struct doc-comment for the three-state semantics.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sessions: Option<Vec<SessionId>>,
pub user_id: UserId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
pub tools: Vec<ToolDescriptionWithSchema>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub hooks: Vec<HookKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub if_match_generation: Option<u64>,
/// Opaque metadata supplied by the tool server at registration time.
/// Applied to every tool in the batch and propagated to
/// `ServerInfo.metadata` in `servers.list` responses.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
/// Per-tool result from a `register_tool` or `register_server` call.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum RegistrationOutcome {
Registered {
tool_id: ToolId,
generation: u64,
},
Updated {
tool_id: ToolId,
generation: u64,
},
Shadowed {
tool_id: ToolId,
reason: String,
},
Rejected {
tool_id: ToolId,
code: String,
message: String,
},
}