saya-agent 0.2.0

Agentic LLM loop and OpenAI-compatible provider clients for SAYA CLI.
Documentation
use super::{AgentError, check_cancelled, emit};
use crate::{
    AgentEvent, AgentEventSink, CancellationToken, ChatMessage, ChatProvider, ChatRequest,
    ProviderError, ProviderEvent, ToolDefinition,
};
use futures_util::StreamExt;

pub(super) async fn receive(
    provider: &dyn ChatProvider,
    model: &str,
    messages: &[ChatMessage],
    definitions: &[ToolDefinition],
    sink: &dyn AgentEventSink,
    cancellation: &CancellationToken,
    events: &mut Vec<AgentEvent>,
) -> Result<ChatMessage, AgentError> {
    let mut stream = provider
        .stream(
            ChatRequest {
                model: model.into(),
                messages: messages.into(),
                tools: definitions.into(),
            },
            cancellation.clone(),
        )
        .await?;
    let (mut content, mut calls, mut complete) = (String::new(), Vec::new(), false);
    while let Some(event) = stream.next().await {
        check_cancelled(cancellation)?;
        match event? {
            ProviderEvent::TextDelta(text) => {
                content.push_str(&text);
                emit(events, sink, AgentEvent::AssistantText { text }).await;
            }
            ProviderEvent::ToolCalls(value) => calls.extend(value),
            ProviderEvent::Done => complete = true,
        }
    }
    if !complete || (content.trim().is_empty() && calls.is_empty()) {
        return Err(AgentError::Provider(ProviderError::InvalidResponse));
    }
    Ok(ChatMessage {
        role: "assistant".into(),
        content,
        tool_calls: calls,
        tool_call_id: None,
    })
}