Skip to main content

proofborne_core/
provider.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use thiserror::Error;
4use uuid::Uuid;
5
6use crate::{ToolCall, ToolDefinition, ToolResult};
7
8/// Capabilities declared by a provider adapter.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(rename_all = "camelCase")]
11pub struct ProviderCapabilities {
12    /// Token-by-token or event streaming.
13    pub streaming: bool,
14    /// Client-executed function calls.
15    pub tools: bool,
16    /// JSON-schema-constrained model output.
17    pub structured_output: bool,
18    /// Non-text input.
19    pub multimodal_input: bool,
20    /// Known context limit, absent when discoverable only per model.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub context_tokens: Option<u64>,
23}
24
25/// Provider-neutral conversation items retained by the session engine.
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
27#[serde(tag = "type", rename_all = "snake_case")]
28pub enum ConversationItem {
29    /// User-authored text.
30    UserText {
31        /// Message body.
32        text: String,
33    },
34    /// Model-authored public text.
35    AssistantText {
36        /// Message body.
37        text: String,
38    },
39    /// Model-requested tool execution.
40    ToolCall {
41        /// Structured call.
42        call: ToolCall,
43    },
44    /// Runtime tool result.
45    ToolResult {
46        /// Structured result.
47        result: ToolResult,
48    },
49}
50
51/// One provider turn request.
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53#[serde(rename_all = "camelCase")]
54pub struct ProviderRequest {
55    /// Proofborne session identifier.
56    pub session_id: Uuid,
57    /// Provider model selector; never silently replaced.
58    pub model: String,
59    /// Stable runtime instructions.
60    pub instructions: String,
61    /// Provider-neutral history.
62    pub items: Vec<ConversationItem>,
63    /// Tools available for this turn.
64    #[serde(default)]
65    pub tools: Vec<ToolDefinition>,
66    /// Optional JSON Schema that the provider must enforce for assistant text.
67    ///
68    /// Adapters that do not support schema-constrained output must return
69    /// [`ProviderError::Unsupported`] instead of silently treating this as a
70    /// plain-text request.
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub output_schema: Option<Value>,
73    /// Optional previous provider response handle.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub previous_response_id: Option<String>,
76}
77
78/// Normalized result of one provider turn.
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80#[serde(rename_all = "camelCase")]
81pub struct ProviderTurn {
82    /// Adapter identifier.
83    pub provider: String,
84    /// Actual model reported by the provider.
85    pub model: String,
86    /// Provider response handle.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub response_id: Option<String>,
89    /// Public model text; hidden reasoning is never requested.
90    #[serde(default)]
91    pub output_text: String,
92    /// Structured client tool calls.
93    #[serde(default)]
94    pub tool_calls: Vec<ToolCall>,
95    /// Why the turn ended.
96    pub stop_reason: StopReason,
97    /// Token accounting when supplied.
98    #[serde(default)]
99    pub usage: TokenUsage,
100    /// Lossless provider payload for fields outside the canonical model.
101    #[serde(default)]
102    pub opaque: Value,
103}
104
105/// Provider-neutral events emitted while one model turn is in progress.
106///
107/// Every incrementally normalized event retains the complete provider frame in
108/// `opaque`. Consumers must treat that value as versioned provider data rather
109/// than infer portable semantics from it.
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111#[serde(tag = "type", rename_all = "camelCase")]
112pub enum ProviderStreamEvent {
113    /// Public assistant text that can be rendered immediately.
114    TextDelta {
115        /// Newly received text; it is not the accumulated response.
116        delta: String,
117        /// Complete provider frame that produced the delta.
118        #[serde(default)]
119        opaque: Value,
120    },
121    /// A client-executed tool call whose stable identity and name are known.
122    ToolCallStarted {
123        /// Provider call identifier used for later deltas and results.
124        id: String,
125        /// Registered tool name.
126        name: String,
127        /// Complete provider frame that announced the call.
128        #[serde(default)]
129        opaque: Value,
130    },
131    /// A partial JSON string for one tool call's arguments.
132    ///
133    /// The delta is deliberately not parsed until the provider marks the call
134    /// complete. Partial JSON is not safe to execute.
135    ToolCallArgumentsDelta {
136        /// Provider call identifier.
137        id: String,
138        /// Newly received JSON fragment.
139        delta: String,
140        /// Complete provider frame that produced the fragment.
141        #[serde(default)]
142        opaque: Value,
143    },
144    /// A fully normalized tool call whose arguments are valid JSON.
145    ToolCallCompleted {
146        /// Structured call ready for runtime validation and policy checks.
147        call: ToolCall,
148        /// Complete provider frame that finalized the call.
149        #[serde(default)]
150        opaque: Value,
151    },
152    /// Provider-reported cumulative token accounting.
153    Usage {
154        /// Latest normalized usage counters.
155        usage: TokenUsage,
156        /// Complete provider frame containing the counters.
157        #[serde(default)]
158        opaque: Value,
159    },
160    /// Canonical terminal result for the provider turn.
161    Completed {
162        /// Fully accumulated turn.
163        turn: ProviderTurn,
164    },
165    /// A well-formed provider event with no lossless canonical equivalent.
166    Opaque {
167        /// Provider event name, or `message` when the transport omitted one.
168        event_type: String,
169        /// Complete provider payload.
170        payload: Value,
171    },
172}
173
174/// Provider-neutral stop reasons.
175#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
176#[serde(rename_all = "snake_case")]
177pub enum StopReason {
178    /// Provider returned a final response.
179    EndTurn,
180    /// Provider requested one or more tools.
181    ToolUse,
182    /// Output limit was reached.
183    MaxTokens,
184    /// Provider refused the task.
185    Refusal,
186}
187
188/// Provider token usage.
189#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
190#[serde(rename_all = "camelCase")]
191pub struct TokenUsage {
192    /// Input tokens.
193    pub input_tokens: u64,
194    /// Output tokens.
195    pub output_tokens: u64,
196    /// Reused/cache-hit tokens.
197    pub cached_tokens: u64,
198    /// Provider-reported reasoning tokens, never reasoning content.
199    pub reasoning_tokens: u64,
200}
201
202/// Failures visible at the provider boundary.
203#[derive(Debug, Error)]
204pub enum ProviderError {
205    /// Configuration or authentication is missing.
206    #[error("provider configuration error: {0}")]
207    Configuration(String),
208    /// Requested canonical capability is unsupported.
209    #[error("provider does not support required capability: {0}")]
210    Unsupported(String),
211    /// Network or HTTP failure.
212    #[error("provider transport failed: {0}")]
213    Transport(String),
214    /// Provider returned a structured API error.
215    #[error("provider API error: {0}")]
216    Api(String),
217    /// Response could not be normalized safely.
218    #[error("provider response was invalid: {0}")]
219    InvalidResponse(String),
220    /// Turn was cancelled.
221    #[error("provider turn cancelled")]
222    Cancelled,
223}