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).
65 lines
2.5 KiB
Rust
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",
|
|
))
|
|
}
|
|
}
|