saya-agent 0.2.0

Agentic LLM loop and OpenAI-compatible provider clients for SAYA CLI.
Documentation
use crate::{ChatMessage, ToolCall, ToolDefinition};
use serde::Serialize;

#[derive(Serialize)]
pub(crate) struct WireMessage {
    pub role: String,
    pub content: String,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub tool_calls: Vec<WireToolCall>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
}

#[derive(Serialize)]
pub(crate) struct WireToolCall {
    pub id: String,
    #[serde(rename = "type")]
    pub kind: String,
    pub function: WireFunction,
}

#[derive(Serialize)]
pub(crate) struct WireFunction {
    pub name: String,
    pub arguments: String,
}

#[derive(Serialize)]
pub(crate) struct WireTool {
    #[serde(rename = "type")]
    pub kind: String,
    pub function: WireToolDefinition,
}

#[derive(Serialize)]
pub(crate) struct WireToolDefinition {
    pub name: String,
    pub description: String,
    pub parameters: serde_json::Value,
}

pub(crate) fn messages(values: Vec<ChatMessage>) -> Vec<WireMessage> {
    values
        .into_iter()
        .map(|message| WireMessage {
            role: message.role,
            content: message.content,
            tool_calls: message.tool_calls.into_iter().map(tool_call).collect(),
            tool_call_id: message.tool_call_id,
        })
        .collect()
}

pub(crate) fn tools(values: Vec<ToolDefinition>) -> Vec<WireTool> {
    values
        .into_iter()
        .map(|tool| WireTool {
            kind: "function".into(),
            function: WireToolDefinition {
                name: tool.name,
                description: tool.description,
                parameters: tool.parameters,
            },
        })
        .collect()
}

fn tool_call(call: ToolCall) -> WireToolCall {
    WireToolCall {
        id: call.id,
        kind: "function".into(),
        function: WireFunction {
            name: call.name,
            arguments: call.arguments.to_string(),
        },
    }
}