supercode-harness 0.4.4

The optional native Supercode agent and tool harness
Documentation
//! Cookbook 02 — a custom tool, end to end, offline.
//!
//! Customizability is the headline feature: you can hand the agent your own
//! tools. Here we define a `word_count` tool and prove the agent actually calls
//! it — using a *scripted* provider so the example runs deterministically with
//! no API key.
//!
//! ```sh
//! cargo run -p supercode-harness --example 02_custom_tool
//! ```

use async_trait::async_trait;
use supercode_harness::tools::{Tool, ToolContext};
use supercode_harness::{Agent, ChatMessage, ChatRequest, Config, Provider, Result, Usage};

/// A tiny tool: count the words in `text`.
struct WordCount;

#[async_trait]
impl Tool for WordCount {
    fn name(&self) -> &str {
        "word_count"
    }
    fn description(&self) -> &str {
        "Count the words in the given text."
    }
    fn parameters(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": { "text": { "type": "string" } },
            "required": ["text"]
        })
    }
    async fn execute(&self, args: serde_json::Value, _ctx: &ToolContext) -> Result<String> {
        let text = args["text"].as_str().unwrap_or("");
        Ok(text.split_whitespace().count().to_string())
    }
}

/// A scripted provider: first turn asks for the tool, second turn answers using
/// the tool result. This stands in for a real model so the example is hermetic.
struct Scripted;

#[async_trait]
impl Provider for Scripted {
    async fn complete(
        &self,
        req: &ChatRequest,
        _on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
    ) -> Result<(ChatMessage, Usage)> {
        // If the last message is a tool result, summarize it. Otherwise call the tool.
        let last_is_tool = matches!(
            req.messages.last().map(|m| m.role),
            Some(supercode_harness::Role::Tool)
        );
        if last_is_tool {
            let n = req
                .messages
                .last()
                .and_then(|m| m.content.clone())
                .unwrap_or_default();
            Ok((
                ChatMessage::assistant(format!("That text has {n} words.")),
                Usage::default(),
            ))
        } else {
            let mut msg = ChatMessage::assistant("");
            msg.tool_calls = Some(vec![supercode_harness::ToolCall {
                id: "call_1".into(),
                kind: "function".into(),
                function: supercode_harness::FunctionCall {
                    name: "word_count".into(),
                    arguments: r#"{"text":"the quick brown fox jumps"}"#.into(),
                },
            }]);
            Ok((msg, Usage::default()))
        }
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut agent = Agent::with_provider(Config::builder().build(), Box::new(Scripted));
    agent.register_tool(WordCount);

    let reply = agent.send("How many words in that sentence?").await?;
    println!("{reply}");
    assert!(
        reply.contains('5'),
        "the agent should have used word_count → 5"
    );
    println!("\n✓ the agent invoked the custom `word_count` tool");
    Ok(())
}