Files
Kigi-CLI/crates/codegen/kigi-acp-lib/src/common.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

115 lines
4.2 KiB
Rust

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`; 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 the kind is recorded. 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 callers that merely propagate the error are
/// unaffected.
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.
/// 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. Output is uncolored: it
/// feeds `tracing::debug!`, which typically lands in log files where ANSI
/// escapes are noise.
#[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
);
}
}