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).
This commit is contained in:
@@ -4,12 +4,9 @@ use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Per-tool wire-traveling capabilities. Defaults conservatively (no
|
||||
/// progress, no cancel, single concurrency, no hooks).
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ToolCapabilities {
|
||||
/// Streaming declaration. `None` — the default for every tool today —
|
||||
/// means the tool never emits partial-result progress.
|
||||
/// `None` means the tool never emits partial-result progress.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub streaming: Option<StreamingSpec>,
|
||||
|
||||
@@ -17,8 +14,7 @@ pub struct ToolCapabilities {
|
||||
#[serde(default)]
|
||||
pub supports_cancel: bool,
|
||||
|
||||
/// Maximum concurrent invocations the tool will accept. `None` is
|
||||
/// unlimited.
|
||||
/// `None` is unlimited.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_concurrency: Option<u32>,
|
||||
|
||||
@@ -26,7 +22,6 @@ pub struct ToolCapabilities {
|
||||
#[serde(default)]
|
||||
pub is_read_only: bool,
|
||||
|
||||
/// Lifecycle hooks the tool opts in to receive.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub hooks: Vec<HookKind>,
|
||||
|
||||
@@ -43,17 +38,14 @@ pub struct ToolCapabilities {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub timeout_ms: Option<u64>,
|
||||
|
||||
/// Multi-agent write-coordination scope. Tools that mutate external
|
||||
/// state must declare `Write` so the computer hub routes them to the
|
||||
/// leader agent only. Absence is treated as `Read`.
|
||||
/// Absence is treated as [`ToolScope::Read`].
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tool_scope: Option<ToolScope>,
|
||||
}
|
||||
|
||||
/// How a tool streams partial results. Declared once in
|
||||
/// [`ToolCapabilities::streaming`] and consumed at the source to stamp a
|
||||
/// self-describing progress envelope; downstream layers dispatch on that
|
||||
/// envelope rather than the tool's identity.
|
||||
/// How a tool streams partial results. The spec is stamped onto a
|
||||
/// self-describing progress envelope at the source, so downstream layers
|
||||
/// dispatch on the envelope rather than on the tool's identity.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StreamingSpec {
|
||||
/// Stable snake_case discriminator the tool stamps on its
|
||||
@@ -67,7 +59,6 @@ pub struct StreamingSpec {
|
||||
pub max_delta_bytes: Option<u32>,
|
||||
}
|
||||
|
||||
/// Lifecycle hook a tool may opt in to receive.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HookKind {
|
||||
@@ -86,21 +77,19 @@ pub enum HookKind {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolScope {
|
||||
/// Tool does not mutate external state.
|
||||
Read,
|
||||
/// Tool mutates external state.
|
||||
Write,
|
||||
}
|
||||
|
||||
/// Per-tool notification schemas. Keys are the notification `kind` strings
|
||||
/// the computer hub validates against.
|
||||
/// Keys are the notification `kind` strings the computer hub validates
|
||||
/// against.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct NotificationSchemas {
|
||||
/// Schemas for notifications the tool emits to subscribers.
|
||||
/// Notifications the tool emits to subscribers.
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub outbound: HashMap<String, serde_json::Value>,
|
||||
|
||||
/// Schemas for notifications the harness sends to the tool.
|
||||
/// Notifications the harness sends to the tool.
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub inbound: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Role of a WebSocket connection. The computer hub uses this to decide
|
||||
/// which methods are valid on a given socket.
|
||||
/// Role of a WebSocket connection; the computer hub decides from it which
|
||||
/// methods are valid on a given socket.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ConnectionKind {
|
||||
@@ -13,9 +13,6 @@ pub enum ConnectionKind {
|
||||
|
||||
/// How the computer hub exposes the registered tool set to the model.
|
||||
///
|
||||
/// `Concise` carries a configurable meta-tool pair so callers can choose
|
||||
/// the model-facing names of the search/invoke meta-tools per session.
|
||||
///
|
||||
/// Wire form is adjacently tagged on `mode`: `Full` serialises as
|
||||
/// `{"mode": "full"}` (an object, not a bare string), and `Concise` as
|
||||
/// `{"mode": "concise", "meta_search": "...", "meta_call": "..."}`.
|
||||
|
||||
@@ -79,7 +79,6 @@ impl JsonRpcId {
|
||||
Self::String(s.into())
|
||||
}
|
||||
|
||||
/// Build a fresh UUID v7-backed id.
|
||||
pub fn new_uuid_v7() -> Self {
|
||||
Self::String(uuid::Uuid::now_v7().to_string())
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
//! than the numeric JSON-RPC `error.code`. The numeric is the JSON-RPC
|
||||
//! envelope code; the string is the Kigi stable identifier.
|
||||
//!
|
||||
//! Implemented as a `&'static [(i32, &'static str)]` table; the table is
|
||||
//! a small fixed set so a linear scan is faster than any
|
||||
//! `HashMap`/`OnceLock`-shaped alternative.
|
||||
//! The mapping is a flat table scanned linearly: the set is small and fixed,
|
||||
//! so that beats any `HashMap`/`OnceLock`-shaped alternative.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -44,24 +43,22 @@ pub const ERROR_CODES: &[(i32, &str)] = &[
|
||||
(-32099, "rate_limited"),
|
||||
];
|
||||
|
||||
/// Returns `None` for strings not in the table. Receivers should fall
|
||||
/// back to `-32603 internal_error` for unknown strings.
|
||||
/// Receivers should fall back to `-32603 internal_error` for strings that
|
||||
/// are not in the table.
|
||||
pub fn numeric_for(code_str: &str) -> Option<i32> {
|
||||
ERROR_CODES
|
||||
.iter()
|
||||
.find_map(|(n, s)| (*s == code_str).then_some(*n))
|
||||
}
|
||||
|
||||
/// Returns `None` for codes not in the table.
|
||||
pub fn string_for(code: i32) -> Option<&'static str> {
|
||||
ERROR_CODES
|
||||
.iter()
|
||||
.find_map(|(n, s)| (*n == code).then_some(*s))
|
||||
}
|
||||
|
||||
/// Numeric code most-appropriate for a [`ToolErrorWire`] variant.
|
||||
/// `Custom` always maps to `-32603 internal_error` since its `code`
|
||||
/// string is not in the table by definition.
|
||||
/// `Custom` always maps to `-32603 internal_error`, since by definition its
|
||||
/// `code` string is not in the table.
|
||||
pub fn from_tool_error_wire(err: &ToolErrorWire) -> i32 {
|
||||
match err {
|
||||
ToolErrorWire::ToolNotFound { .. } => -32011,
|
||||
@@ -138,7 +135,6 @@ pub struct WorkspaceUnavailableDetails {
|
||||
pub retryable: bool,
|
||||
}
|
||||
|
||||
/// Build the recognizable "workspace gone" error as a [`ToolErrorWire::Custom`].
|
||||
pub fn workspace_unavailable_wire(
|
||||
reason: WorkspaceGoneReason,
|
||||
phase: WorkspaceGonePhase,
|
||||
@@ -222,7 +218,6 @@ mod tests {
|
||||
) else {
|
||||
panic!("expected Custom variant");
|
||||
};
|
||||
// Exact, tenant-data-free contract.
|
||||
assert_eq!(message, WORKSPACE_UNAVAILABLE_MESSAGE);
|
||||
}
|
||||
|
||||
@@ -264,7 +259,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn custom_variant_tolerates_unknown_future_details_shape() {
|
||||
// An unknown subcode + richer future details must still deserialize rather than failing the frame.
|
||||
// An unknown subcode carrying richer details must still deserialize
|
||||
// rather than failing the whole frame.
|
||||
let future = json!({
|
||||
"code": "custom",
|
||||
"subcode": "some_future_subcode",
|
||||
@@ -290,7 +286,6 @@ mod tests {
|
||||
.is_some(),
|
||||
"unknown details fields are preserved",
|
||||
);
|
||||
// Re-serialization preserves the unknown fields.
|
||||
let reser = serde_json::to_value(&wire).unwrap();
|
||||
assert_eq!(
|
||||
reser["details"]["extra_new_field"]["nested"],
|
||||
|
||||
@@ -52,9 +52,7 @@ pub enum ToolErrorWire {
|
||||
#[error("behavior_version unsupported")]
|
||||
BehaviorVersionUnsupported { tool_id: ToolId, requested: String },
|
||||
|
||||
/// Render-card budget exceeded for the current session. `card_id`
|
||||
/// carries the offending render-card identifier when known; `reason`
|
||||
/// is a free-form human-readable explanation.
|
||||
/// Render-card budget exceeded for the current session.
|
||||
#[error("render limited for {tool_id}: {reason}")]
|
||||
RenderLimited {
|
||||
tool_id: ToolId,
|
||||
@@ -74,10 +72,9 @@ pub enum ToolErrorWire {
|
||||
Internal {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
request_id: Option<RequestId>,
|
||||
/// Bounded, human-readable cause of the internal error. Optional for
|
||||
/// wire compatibility with older peers; producers SHOULD populate it
|
||||
/// (truncated at the producer) so receivers can distinguish failure
|
||||
/// modes without correlating server logs.
|
||||
/// Optional for wire compatibility with older peers; producers SHOULD
|
||||
/// populate it (truncated at the producer) so receivers can distinguish
|
||||
/// failure modes without correlating server logs.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
detail: Option<String>,
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
output_wire::ToolOutputWire,
|
||||
};
|
||||
|
||||
// ── Tool call params / result / progress ─────────────────────────────────
|
||||
// Tool call params / result / progress
|
||||
|
||||
/// `tool.call` (harness → service) and `tool_call_request` (service →
|
||||
/// tool_server) share the same params shape; `tool_call_id` is preserved
|
||||
@@ -59,7 +59,7 @@ pub struct ToolCallResult {
|
||||
pub chat_completion_output: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
// ── Trace donation ────────────────────────────────────────────────────────
|
||||
// Trace donation
|
||||
|
||||
/// Hub rejects oversized batches wholesale; donors chunk before encoding.
|
||||
pub const MAX_SPANS_PER_DONATION: usize = 512;
|
||||
@@ -77,7 +77,7 @@ pub struct TracesDonateParams {
|
||||
pub otlp_request: String,
|
||||
}
|
||||
|
||||
// ── Log donation ──────────────────────────────────────────────────────────
|
||||
// Log donation
|
||||
|
||||
/// Hub rejects oversized batches wholesale; donors chunk before encoding.
|
||||
/// The 1 MiB [`MAX_DONATION_BYTES`] decoded-size cap is the real bound; this
|
||||
@@ -94,7 +94,7 @@ pub struct LogsDonateParams {
|
||||
pub otlp_request: String,
|
||||
}
|
||||
|
||||
// ── Metric donation ───────────────────────────────────────────────────────
|
||||
// Metric donation
|
||||
|
||||
/// Hub rejects oversized batches wholesale; donors chunk before encoding.
|
||||
/// Secondary guard alongside the 1 MiB [`MAX_DONATION_BYTES`] decoded-size cap.
|
||||
@@ -189,7 +189,7 @@ pub struct SystemNotifyParams {
|
||||
pub request_id: Option<String>,
|
||||
}
|
||||
|
||||
// ── Registration frames ──────────────────────────────────────────────────
|
||||
// Registration frames
|
||||
|
||||
/// `register_tool` params — single-tool sugar over `register_server`.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -217,7 +217,7 @@ pub struct UnregisterServerParams {
|
||||
pub server_id: ServerId,
|
||||
}
|
||||
|
||||
// ── Per-tool session binding ───────────────────────────────────────────────
|
||||
// Per-tool session binding
|
||||
|
||||
/// `bind_tool_session` params — add `session_id` to a registered tool's
|
||||
/// per-tool session set.
|
||||
@@ -229,7 +229,6 @@ pub struct UnregisterServerParams {
|
||||
/// typically omitted on connection-control frames.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BindToolSessionParams {
|
||||
/// The tool whose session set is being mutated.
|
||||
pub tool_id: ToolId,
|
||||
/// The session id to add to the tool's session set. Must already
|
||||
/// be in the connection's bound-session set.
|
||||
@@ -249,7 +248,7 @@ pub struct BindToolSessionParams {
|
||||
/// `ServerError::ToolBindingConflict` (-32600) so the contended caller
|
||||
/// sees a wire-level error frame with a dedicated code instead of a
|
||||
/// quietly-buried ack outcome — mirroring it would re-introduce the
|
||||
/// `UnknownTool`-overload ambiguity the dedicated code was added to fix.
|
||||
/// `UnknownTool`-overload ambiguity the dedicated code exists to fix.
|
||||
/// - `SessionNotBound` is router-injected by the per-frame envelope
|
||||
/// pre-check (the connection's bound-session set is router state, not
|
||||
/// registry state) and never originates from the registry call.
|
||||
@@ -285,7 +284,6 @@ pub struct BindToolSessionAck {
|
||||
/// the calling-frame routing scope and serves a different concept.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UnbindToolSessionParams {
|
||||
/// The tool whose session set is being mutated.
|
||||
pub tool_id: ToolId,
|
||||
/// The session id to remove from the tool's session set.
|
||||
pub session_id: SessionId,
|
||||
@@ -309,7 +307,7 @@ pub struct UnbindToolSessionAck {
|
||||
pub outcome: ToolSessionUnbindOutcome,
|
||||
}
|
||||
|
||||
// ── Server discovery + binding ────────────────────────────────────────────
|
||||
// Server discovery + binding
|
||||
|
||||
/// `servers.list` params — discover available tool servers for the
|
||||
/// authenticated user.
|
||||
@@ -345,7 +343,6 @@ pub struct ServersListResult {
|
||||
pub struct ServerBindParams {
|
||||
/// Which tool server to bind (its server_id from `servers.list`).
|
||||
pub server_id: ServerId,
|
||||
/// The harness session to bind tools to.
|
||||
pub session_id: SessionId,
|
||||
}
|
||||
|
||||
@@ -355,12 +352,12 @@ pub struct ServerBindParams {
|
||||
pub enum ServerBindOutcome {
|
||||
/// Tools successfully bound to the session.
|
||||
Bound,
|
||||
/// Tools were already bound to this session.
|
||||
/// Tools already bound to this session.
|
||||
AlreadyBound,
|
||||
/// No server with this server_id found.
|
||||
ServerNotFound,
|
||||
/// A server was located and the bind forwarded, but it did not complete:
|
||||
/// the ack timed out, the transport send/delivery failed, or the ack was
|
||||
/// Server found and bind forwarded, but bind did not complete:
|
||||
/// ack timed out, transport send/delivery failed, or ack was
|
||||
/// malformed or an explicit error. Distinct from `ServerNotFound`, which
|
||||
/// means no such server is registered.
|
||||
Unavailable,
|
||||
@@ -393,7 +390,7 @@ pub struct ServerUnbindAck {
|
||||
pub outcome: ServerUnbindOutcome,
|
||||
}
|
||||
|
||||
// ── List & search ────────────────────────────────────────────────────────
|
||||
// List & search
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ToolsListParams {
|
||||
@@ -432,7 +429,7 @@ pub struct ToolsSearchResultBody {
|
||||
pub is_ready: bool,
|
||||
}
|
||||
|
||||
// ── Session lifecycle ────────────────────────────────────────────────────
|
||||
// Session lifecycle
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SessionOpenParams {
|
||||
@@ -467,9 +464,8 @@ pub struct SessionOpenResult {}
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SessionBindServerParams {
|
||||
pub server_id: ServerId,
|
||||
/// Working directory for the session. The tool server creates a
|
||||
/// session rooted at this path. When absent, the server's default
|
||||
/// CWD is used.
|
||||
/// Working directory for the session. When absent, the tool server's
|
||||
/// default CWD is used.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cwd: Option<String>,
|
||||
/// Opaque metadata passed through to the tool server (sandbox_id,
|
||||
@@ -543,7 +539,7 @@ pub enum AttachRoute {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// ── Simplified lifecycle ─────────────────────────────────────────────────
|
||||
// Simplified lifecycle
|
||||
|
||||
/// `serve` params (server → hub). Full tool snapshot for a session.
|
||||
///
|
||||
@@ -558,13 +554,12 @@ pub struct ServeParams {
|
||||
/// Reply to [`ServeParams`].
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ServeResult {
|
||||
/// Number of tools accepted (informational).
|
||||
#[serde(default)]
|
||||
pub accepted: usize,
|
||||
/// Tool IDs that were added relative to the previous snapshot.
|
||||
/// Tool IDs added relative to the previous snapshot.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub added: Vec<ToolId>,
|
||||
/// Tool IDs that were removed relative to the previous snapshot.
|
||||
/// Tool IDs removed relative to the previous snapshot.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub removed: Vec<ToolId>,
|
||||
}
|
||||
@@ -606,7 +601,7 @@ pub struct SessionBindResult {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SessionUnbindParams {}
|
||||
|
||||
// ── Subscriptions ────────────────────────────────────────────────────────
|
||||
// Subscriptions
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SubscribeNotificationsParams {
|
||||
@@ -673,11 +668,11 @@ pub struct UnsubscribeNotificationsParams {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum UnsubscribeOutcome {
|
||||
/// Subscription was present and was removed.
|
||||
/// Subscription was present and the service removed it.
|
||||
Unsubscribed,
|
||||
/// Subscription was not present; no-op.
|
||||
NotSubscribed,
|
||||
/// Subscription was removed by the service because the subscriber's
|
||||
/// The service removed the subscription because the subscriber's
|
||||
/// outbound mpsc was full or dropped during fan-out.
|
||||
Evicted,
|
||||
}
|
||||
@@ -690,7 +685,7 @@ pub struct UnsubscribeAck {
|
||||
pub subscription_id: String,
|
||||
}
|
||||
|
||||
// ── Hooks ────────────────────────────────────────────────────────────────
|
||||
// Hooks
|
||||
|
||||
/// `hook` frame body, routed in both directions through the hub: harness →
|
||||
/// tool-server for forward hooks (e.g. `Cancel`, `SessionEnded`), and
|
||||
@@ -813,7 +808,7 @@ pub struct HookReplyFrame {
|
||||
pub result: serde_json::Value,
|
||||
}
|
||||
|
||||
// ── Service → harness pushes ─────────────────────────────────────────────
|
||||
// Service → harness pushes
|
||||
|
||||
/// `tools_changed` body — the active tool set for `session_id` changed.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -827,7 +822,7 @@ pub struct ToolsChanged {
|
||||
pub updated: Vec<ToolId>,
|
||||
}
|
||||
|
||||
// ── Tool server status lifecycle ──────────────────────────────────────
|
||||
// Tool server status lifecycle
|
||||
|
||||
/// Lifecycle status of a tool server connection.
|
||||
///
|
||||
@@ -937,7 +932,7 @@ pub enum ToolServerDisconnectReason {
|
||||
ConnectionLost,
|
||||
}
|
||||
|
||||
// ── Heartbeat ────────────────────────────────────────────────────────────
|
||||
// Heartbeat
|
||||
//
|
||||
// PingFrame / PongFrame carry a `method` discriminator on the wire so
|
||||
// any receiver (hub or SDK) can route them through a method-based demux.
|
||||
@@ -975,7 +970,7 @@ impl PongFrame {
|
||||
}
|
||||
}
|
||||
|
||||
// -- Custom Serialize: always includes `"method"` on the wire. -----------
|
||||
// Custom Serialize: always includes `"method"` on the wire.
|
||||
|
||||
impl serde::Serialize for PingFrame {
|
||||
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
@@ -997,7 +992,7 @@ impl serde::Serialize for PongFrame {
|
||||
}
|
||||
}
|
||||
|
||||
// -- Custom Deserialize: accepts with or without `method` for compat. ----
|
||||
// Custom Deserialize: accepts with or without `method` for compat.
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for PingFrame {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||
@@ -1068,8 +1063,6 @@ mod tests {
|
||||
ToolCallId::new_v7()
|
||||
}
|
||||
|
||||
// ── HookFrame constructors ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn hook_cancel_sets_tool_and_call_ids() {
|
||||
let hook = HookFrame::cancel(sid(), tid(), cid());
|
||||
@@ -1123,8 +1116,6 @@ mod tests {
|
||||
assert_eq!(hook, back);
|
||||
}
|
||||
|
||||
// ── ToolNotificationFrame constructors ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn notification_custom_sets_wire_shape() {
|
||||
let frame = ToolNotificationFrame::custom(tid(), "echo.status", json!({"status": "idle"}));
|
||||
@@ -1187,8 +1178,6 @@ mod tests {
|
||||
assert_eq!(back, params);
|
||||
}
|
||||
|
||||
// ── Donation params ────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn logs_donate_params_round_trips() {
|
||||
let params = super::LogsDonateParams {
|
||||
@@ -1211,8 +1200,6 @@ mod tests {
|
||||
assert_eq!(back, params);
|
||||
}
|
||||
|
||||
// ── ToolServerStatusPayload ────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn tool_server_lifecycle_status_serde_snake_case() {
|
||||
let status = super::ToolServerLifecycleStatus::ShuttingDown;
|
||||
@@ -1470,8 +1457,6 @@ mod tests {
|
||||
assert_eq!(frame, back);
|
||||
}
|
||||
|
||||
// ── hook_id backward-compat ─────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn hook_frame_missing_hook_id_deserializes_as_none() {
|
||||
let v = json!({
|
||||
|
||||
@@ -4,9 +4,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ConnectionId, ConnectionKind, ServerId, UserId};
|
||||
|
||||
/// Wire-protocol version both ends speak. Bumped when an incompatible
|
||||
/// schema change lands; minor additions go through capability
|
||||
/// negotiation rather than a version bump.
|
||||
/// Bumped only for incompatible schema changes; additive changes go through
|
||||
/// capability negotiation instead.
|
||||
pub const PROTOCOL_VERSION: &str = "1.0.0";
|
||||
|
||||
/// First frame sent by the client after the WebSocket upgrade succeeds.
|
||||
@@ -14,15 +13,13 @@ pub const PROTOCOL_VERSION: &str = "1.0.0";
|
||||
/// No session ids are carried at handshake time. The connection starts with
|
||||
/// an empty bound-session set and binds sessions dynamically over its
|
||||
/// lifetime via `register_session` / `unregister_session` JSON-RPC calls.
|
||||
///
|
||||
/// Tool-server connections carry `server_id` so the hub can
|
||||
/// identify the server without a separate `register_server` call.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct HelloMsg {
|
||||
pub protocol_version: String,
|
||||
pub kind: ConnectionKind,
|
||||
/// Stable server identity. Only set for
|
||||
/// [`ConnectionKind::ToolServer`] connections.
|
||||
/// Stable server identity, set only for [`ConnectionKind::ToolServer`]
|
||||
/// connections, so the hub can identify the server without a separate
|
||||
/// `register_server` call.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub server_id: Option<ServerId>,
|
||||
/// One-line server description for `servers.list`.
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Internally-tagged hook payload. New variants land alongside `Custom`,
|
||||
/// which keeps unknown future kinds round-trippable.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum HookEvent {
|
||||
/// Cancel an in-flight call. The owning `tool_call_id` travels in the
|
||||
/// enclosing `hook` frame.
|
||||
/// The `tool_call_id` this cancels travels in the enclosing `hook` frame.
|
||||
Cancel,
|
||||
Pause,
|
||||
Resume,
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
/// Errors produced by id constructors and validators.
|
||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum IdError {
|
||||
#[error("identifier must not be empty")]
|
||||
@@ -56,7 +55,6 @@ macro_rules! opaque_id {
|
||||
pub struct $name(String);
|
||||
|
||||
impl $name {
|
||||
/// Construct, validating the id's invariants.
|
||||
pub fn new(value: impl Into<String>) -> Result<Self, IdError> {
|
||||
let value = value.into();
|
||||
ensure_non_empty(&value)?;
|
||||
@@ -132,7 +130,6 @@ opaque_id!(
|
||||
);
|
||||
|
||||
impl ToolCallId {
|
||||
/// Generate a fresh UUID v7-backed `ToolCallId`.
|
||||
pub fn new_v7() -> Self {
|
||||
Self(uuid::Uuid::now_v7().to_string())
|
||||
}
|
||||
@@ -209,9 +206,6 @@ opaque_id!(
|
||||
|
||||
/// Per-connection monotonic notification sequence (starts at 0 on every new
|
||||
/// connection).
|
||||
///
|
||||
/// The inner `u64` is private so `new`, `From<u64>`, and `Default` are the
|
||||
/// only construction paths.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize,
|
||||
)]
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
//! Tool wire-protocol types.
|
||||
//!
|
||||
//! Identifier newtypes, registration payloads, capabilities, hook events,
|
||||
//! handshake messages, the JSON-RPC 2.0 envelope and method catalog, the
|
||||
//! `ToolErrorWire` / `ToolOutputWire` / `WireToolNotification` wire enums,
|
||||
//! every method's `params` / `result` payload struct, and the numeric ↔
|
||||
//! string error-code mapping.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ define_methods! {
|
||||
ToolServerGetStatus => "tool_server.get_status",
|
||||
ToolServerEvict => "tool_server.evict",
|
||||
|
||||
// ── Session lifecycle ───────────────────────────────────────────
|
||||
// Session lifecycle
|
||||
|
||||
/// Full tool snapshot for a session (server → hub). Idempotent:
|
||||
/// re-sending replaces the tool set; the hub diffs and emits
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Adjacent-tagged notification wire wrapper.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "shape", content = "value", rename_all = "snake_case")]
|
||||
pub enum WireToolNotification {
|
||||
@@ -72,9 +71,8 @@ pub const fn known_notification_kinds() -> &'static [&'static str] {
|
||||
}
|
||||
|
||||
/// Reject custom notification kinds whose name shadows a known PascalCase
|
||||
/// variant. Runs at notification-emit time; an empty `kind` is accepted
|
||||
/// here (the producer is responsible for validating that the field is
|
||||
/// non-empty).
|
||||
/// variant. An empty `kind` is accepted here; the producer is responsible
|
||||
/// for validating that the field is non-empty.
|
||||
pub fn check_custom_kind(kind: &str) -> Result<(), KnownVariantCollision> {
|
||||
if KNOWN_NOTIFICATION_KINDS.contains(&kind) {
|
||||
Err(KnownVariantCollision {
|
||||
|
||||
@@ -14,9 +14,8 @@ pub enum TransportKind {
|
||||
Remote,
|
||||
}
|
||||
|
||||
/// A single tool's wire description plus optional schema and capability
|
||||
/// metadata. The `tool_id` is **not** stored explicitly — it is derived
|
||||
/// from `description.{namespace, name}` via [`Self::derive_tool_id`].
|
||||
/// 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,
|
||||
@@ -29,8 +28,6 @@ pub struct ToolDescriptionWithSchema {
|
||||
}
|
||||
|
||||
impl ToolDescriptionWithSchema {
|
||||
/// Derive the canonical `ToolId`.
|
||||
///
|
||||
/// 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`].
|
||||
@@ -69,8 +66,7 @@ pub struct ToolRegistration {
|
||||
/// enforces this at register-tool time and rejects mismatches with
|
||||
/// `InvalidRequest`.
|
||||
pub tool_id: ToolId,
|
||||
/// Per-tool session set. See struct doc-comment for the
|
||||
/// `None` / `Some(vec![])` / `Some(vec![...])` semantics.
|
||||
/// 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,
|
||||
@@ -95,9 +91,6 @@ pub struct ToolRegistration {
|
||||
}
|
||||
|
||||
impl ToolRegistration {
|
||||
/// Derive the canonical `ToolId` from `description.{namespace, name}`.
|
||||
/// The `tool_id` payload field MUST equal this value; the IC service
|
||||
/// router enforces the invariant at register-tool time.
|
||||
pub fn derive_tool_id(&self) -> Result<ToolId, IdError> {
|
||||
match &self.description.namespace {
|
||||
Some(ns) => ToolId::new(format!("{ns}:{}", self.description.name)),
|
||||
@@ -110,17 +103,12 @@ impl ToolRegistration {
|
||||
/// `sessions` value; per-tool outcomes are reported individually via
|
||||
/// [`RegistrationOutcome`].
|
||||
///
|
||||
/// `sessions` follows the same three-state semantics as
|
||||
/// [`ToolRegistration::sessions`]: `None` means "no change" (preserves
|
||||
/// existing per-tool session bindings on a re-register), `Some(vec![])`
|
||||
/// means "unbind every session for every tool in this batch", and
|
||||
/// `Some(vec![...])` means "replace each tool's session set with
|
||||
/// exactly these ids".
|
||||
/// `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,
|
||||
/// Per-batch session set. See struct doc-comment for `None` /
|
||||
/// `Some(vec![])` / `Some(vec![...])` semantics.
|
||||
/// 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,
|
||||
|
||||
@@ -17,8 +17,6 @@ pub enum RegistryError {
|
||||
#[error("tool already registered: {tool_id}")]
|
||||
AlreadyRegistered { tool_id: ToolId },
|
||||
|
||||
/// The registration's session does not match the connection's bound
|
||||
/// session.
|
||||
#[error("session mismatch: token session={token_session}, registration session={reg_session}")]
|
||||
SessionMismatch {
|
||||
token_session: SessionId,
|
||||
@@ -41,7 +39,7 @@ pub enum RegistryError {
|
||||
#[error("invalid description: {message}")]
|
||||
InvalidDescription { message: String },
|
||||
|
||||
/// `if_match_generation` precondition failed.
|
||||
/// The `if_match_generation` precondition failed.
|
||||
#[error("stale generation: expected={expected}, actual={actual}")]
|
||||
StaleGeneration { expected: u64, actual: u64 },
|
||||
}
|
||||
|
||||
@@ -98,8 +98,6 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
// ── SessionEvent round-trip tests ────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn turn_started_round_trip() {
|
||||
let event = SessionEvent::TurnStarted {
|
||||
@@ -226,8 +224,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ── #[serde(other)] backward-compat ─────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn unknown_event_type_deserializes_as_unknown() {
|
||||
let v = json!({ "event_type": "some_future_event", "extra": 123 });
|
||||
@@ -242,8 +238,6 @@ mod tests {
|
||||
assert_eq!(event, SessionEvent::Unknown);
|
||||
}
|
||||
|
||||
// ── ToolCallOutcome serialization ───────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn tool_call_outcome_snake_case() {
|
||||
for (variant, expected) in [
|
||||
@@ -259,8 +253,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ── SessionPhase serialization ──────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn session_phase_snake_case() {
|
||||
for (variant, expected) in [
|
||||
@@ -277,8 +269,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Forward-compat: inner enum Unknown ──────────────────────────
|
||||
|
||||
#[test]
|
||||
fn tool_call_outcome_unknown_variant_on_future_value() {
|
||||
let back: ToolCallOutcome = serde_json::from_value(json!("timeout")).unwrap();
|
||||
@@ -327,16 +317,12 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Unknown variant serialization ───────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn unknown_variant_serializes_as_expected() {
|
||||
let v = serde_json::to_value(SessionEvent::Unknown).unwrap();
|
||||
assert_eq!(v, json!({"event_type": "unknown"}));
|
||||
}
|
||||
|
||||
// ── Extra/unknown fields on known variants ──────────────────────
|
||||
|
||||
#[test]
|
||||
fn extra_fields_ignored_on_known_variant() {
|
||||
let v = json!({
|
||||
@@ -356,8 +342,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Negative: missing required fields ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn turn_ended_missing_required_field_rejected() {
|
||||
let v = json!({
|
||||
@@ -371,8 +355,6 @@ mod tests {
|
||||
assert!(serde_json::from_value::<SessionEvent>(v).is_err());
|
||||
}
|
||||
|
||||
// ── Boundary values ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn turn_number_zero_and_max() {
|
||||
for turn_number in [0, u64::MAX] {
|
||||
|
||||
@@ -42,8 +42,8 @@ pub struct BeforeTurnPayload {
|
||||
/// Whether the session is in YOLO / auto-approve mode.
|
||||
#[serde(default)]
|
||||
pub yolo_mode: bool,
|
||||
// ── Extended fields (workspace mirrors these into `events.jsonl`);
|
||||
// all `#[serde(default)]` for old-shell / old-workspace interop. ──
|
||||
// Extended fields (workspace mirrors these into `events.jsonl`);
|
||||
// all `#[serde(default)]` for old shell / old workspace interop.
|
||||
/// Mirrors `Event::TurnStarted::conversation_message_count`.
|
||||
#[serde(default)]
|
||||
pub conversation_message_count: usize,
|
||||
@@ -86,7 +86,6 @@ impl Default for BeforeTurnPayload {
|
||||
pub struct AfterTurnPayload {
|
||||
/// Same turn counter as the preceding `before_turn`.
|
||||
pub turn_number: u64,
|
||||
/// High-level outcome of the turn.
|
||||
pub outcome: TurnHookOutcome,
|
||||
/// Wall-clock duration of the turn in milliseconds.
|
||||
pub duration_ms: u64,
|
||||
@@ -128,7 +127,6 @@ pub enum TurnHookOutcome {
|
||||
Completed,
|
||||
/// Turn was cancelled by the user (Ctrl+C / abort).
|
||||
Cancelled,
|
||||
/// Turn ended due to an error.
|
||||
Error,
|
||||
}
|
||||
|
||||
@@ -152,9 +150,7 @@ pub enum TurnHookRequest {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum InjectionRole {
|
||||
/// Append as a system turn.
|
||||
System,
|
||||
/// Append as a developer turn.
|
||||
Developer,
|
||||
/// Append as a user turn (e.g. a `<system-reminder>`-wrapped message).
|
||||
User,
|
||||
@@ -164,9 +160,7 @@ pub enum InjectionRole {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct HookInjection {
|
||||
/// Role to append the content as.
|
||||
pub role: InjectionRole,
|
||||
/// Verbatim turn content.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
@@ -188,10 +182,8 @@ pub enum TurnControl {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct HookReply {
|
||||
/// Turns to append before the next sampling step, in order.
|
||||
#[serde(default)]
|
||||
pub injections: Vec<HookInjection>,
|
||||
/// Optional loop-control override.
|
||||
#[serde(default)]
|
||||
pub control: TurnControl,
|
||||
/// Artifact-handling ack for a [`TurnHookRequest::After`] request; `None`
|
||||
|
||||
Reference in New Issue
Block a user