Skip to main content

scv_core/
provider.rs

1//! The [`Provider`] trait: one model request and its streamed response.
2
3use std::sync::Arc;
4
5use async_trait::async_trait;
6use thiserror::Error;
7use tokio_util::sync::CancellationToken;
8
9use crate::{Message, ToolCall, ToolSpec};
10
11/// Token counts a provider reported; `None` when it reported none.
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct Usage {
14    pub input_tokens: Option<u64>,
15    pub output_tokens: Option<u64>,
16}
17
18impl Usage {
19    pub(crate) fn add(&mut self, other: &Self) {
20        self.input_tokens = add_optional(self.input_tokens, other.input_tokens);
21        self.output_tokens = add_optional(self.output_tokens, other.output_tokens);
22    }
23}
24
25fn add_optional(left: Option<u64>, right: Option<u64>) -> Option<u64> {
26    match (left, right) {
27        (None, None) => None,
28        (left, right) => Some(left.unwrap_or(0).saturating_add(right.unwrap_or(0))),
29    }
30}
31
32/// One model request: the system prompt, the selected history, and the tools.
33#[derive(Debug, Clone)]
34pub struct ProviderRequest {
35    pub system_prompt: String,
36    pub messages: Vec<Message>,
37    pub tools: Vec<ToolSpec>,
38}
39
40/// The model's complete answer to one request.
41#[derive(Debug, Clone)]
42pub struct AssistantResponse {
43    pub content: String,
44    pub tool_calls: Vec<ToolCall>,
45    pub usage: Usage,
46}
47
48/// What kind of failure a [`ProviderError`] is; the runtime maps each to an
49/// [`AgentError`](crate::AgentError).
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum ProviderErrorKind {
52    Provider,
53    ResponseLimit,
54    ToolLimit,
55    Cancelled,
56}
57
58#[derive(Debug, Error)]
59#[error("{message}")]
60pub struct ProviderError {
61    pub kind: ProviderErrorKind,
62    pub message: String,
63}
64
65impl ProviderError {
66    pub fn new(kind: ProviderErrorKind, message: impl Into<String>) -> Self {
67        Self {
68            kind,
69            message: message.into(),
70        }
71    }
72}
73
74/// Where a provider streams answer text as it arrives. An error from
75/// [`push`](TextDeltaSink::push) means stop: the turn was cancelled or hit a
76/// limit.
77#[async_trait]
78pub trait TextDeltaSink: Send + Sync {
79    async fn push(&self, delta: &str) -> Result<(), ProviderError>;
80}
81
82/// A model backend. [`complete`](Provider::complete) sends one request,
83/// streams text deltas to `deltas`, and returns the full answer with any tool
84/// calls. It must stop promptly when `cancellation` fires.
85#[async_trait]
86pub trait Provider: Send + Sync {
87    /// The model name, for display.
88    fn model(&self) -> &str;
89
90    async fn complete(
91        &self,
92        request: ProviderRequest,
93        deltas: Arc<dyn TextDeltaSink>,
94        cancellation: CancellationToken,
95    ) -> Result<AssistantResponse, ProviderError>;
96}