Files
Kigi-CLI/crates/common/kigi-tool-runtime/src/dispatch.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

65 lines
2.5 KiB
Rust

//! Object-safe `ToolDispatch` trait — the runtime contract for handling tool calls.
//!
//! `Tool` itself is not object-safe (associated `Args` / `Output` types), so
//! implementations expose a JSON-typed surface and rely on per-tool adapters
//! to encode/decode at the boundary. The default `call_terminal` impl drains
//! the stream so callers that only need the result avoid `futures` internals.
//!
//! This crate is upstream of every concrete impl. Docs here describe trait
//! semantics in terms of "the runtime" or "the implementation" — concrete
//! dispatch routers live downstream and are intentionally not named here.
use async_trait::async_trait;
use futures::StreamExt;
use serde_json::Value;
use kigi_tool_protocol::ToolId;
use crate::context::ToolCallContext;
use crate::error::ToolError;
use crate::tool::{ToolStream, ToolStreamItem, TypedToolOutput};
/// Object-safe tool dispatch interface.
///
/// Implementations route `tool_id` to the correct tool, decode `args`
/// against the tool's typed `Args`, and return the streaming result as
/// [`TypedToolOutput`] — preserving model-facing content blocks and
/// optional chat-completion metadata end-to-end. Raw `Value` only appears
/// at JSON-RPC wire encode/decode boundaries.
#[async_trait]
pub trait ToolDispatch: Send + Sync {
/// Streaming dispatch. The returned stream MUST end with exactly one
/// `Terminal` item per the [`ToolStream`] invariant.
async fn call(
&self,
tool_id: ToolId,
args: Value,
ctx: ToolCallContext,
) -> ToolStream<TypedToolOutput>;
/// Drain the stream and return only the terminal result.
///
/// Default impl discards `Progress` items and short-circuits on the
/// first `Terminal`. A stream that ends without a `Terminal` is a
/// protocol violation; the default surfaces
/// `ToolError::Custom { code: "stream_no_terminal", ... }`.
async fn call_terminal(
&self,
tool_id: ToolId,
args: Value,
ctx: ToolCallContext,
) -> Result<TypedToolOutput, ToolError> {
let mut stream = self.call(tool_id, args, ctx).await;
while let Some(item) = stream.next().await {
match item {
ToolStreamItem::Progress(_) => continue,
ToolStreamItem::Terminal(result) => return result,
}
}
Err(ToolError::custom(
"stream_no_terminal",
"dispatch stream ended without a terminal item",
))
}
}