Skip to main content

kiss_agent/
tool.rs

1//! The tool contract used by the agent runtime.
2
3use kiss_ai::{ContentBlock, ToolDef, Usage};
4use serde_json::Value;
5use std::sync::Arc;
6use tokio_util::sync::CancellationToken;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum ExecutionMode {
10    Sequential,
11    #[default]
12    Parallel,
13}
14
15#[derive(Debug, Clone, Default)]
16pub struct ToolResult {
17    /// Text or image content returned to the model.
18    pub content: Vec<ContentBlock>,
19    /// Structured details for logs / UI rendering (never sent to the model).
20    pub details: Value,
21    /// Usage from nested LLM work performed by the tool, if any.
22    pub usage: Option<Usage>,
23    /// Hint that the agent should stop after the current tool batch.
24    pub terminate: bool,
25}
26
27impl ToolResult {
28    pub fn text(text: impl Into<String>) -> Self {
29        ToolResult {
30            content: vec![ContentBlock::text(text)],
31            ..Default::default()
32        }
33    }
34
35    pub fn output_text(&self) -> String {
36        self.content
37            .iter()
38            .filter_map(|c| match c {
39                ContentBlock::Text { text, .. } => Some(text.as_str()),
40                _ => None,
41            })
42            .collect::<Vec<_>>()
43            .join("\n")
44    }
45}
46
47/// Sink for streaming partial tool results (e.g. live bash output).
48pub type ToolUpdateSink = Arc<dyn Fn(ToolResult) + Send + Sync>;
49
50#[async_trait::async_trait]
51pub trait AgentTool: Send + Sync {
52    fn name(&self) -> &str;
53    fn label(&self) -> &str {
54        self.name()
55    }
56    fn description(&self) -> String;
57    /// JSON schema object describing the arguments.
58    fn parameters(&self) -> Value;
59    fn execution_mode(&self) -> ExecutionMode {
60        ExecutionMode::Parallel
61    }
62    /// Normalize raw arguments before schema validation (compat shims).
63    fn prepare_arguments(&self, args: Value) -> Value {
64        args
65    }
66    /// Execute. Return Err on failure. The loop converts it into an error
67    /// tool result visible to the model.
68    async fn execute(
69        &self,
70        tool_call_id: &str,
71        args: Value,
72        cancel: CancellationToken,
73        on_update: Option<ToolUpdateSink>,
74    ) -> anyhow::Result<ToolResult>;
75
76    fn to_def(&self) -> ToolDef {
77        ToolDef {
78            name: self.name().to_string(),
79            description: self.description(),
80            parameters: self.parameters(),
81        }
82    }
83}
84
85pub type DynTool = Arc<dyn AgentTool>;