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    /// In-memory provider response handle; runtimes must not persist the raw value.
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 in-memory provider payload for fields outside the canonical model.
101    ///
102    /// Runtimes must remove this field before persisting events or proof material.
103    #[serde(default)]
104    pub opaque: Value,
105}
106
107/// Provider-neutral events emitted while one model turn is in progress.
108///
109/// Adapters retain complete provider frames in `opaque` while normalizing a live
110/// stream. Consumers may inspect that in-memory value, but runtimes must strip it
111/// before persisting events, checkpoints, proof bundles, or generated reports.
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
113#[serde(tag = "type", rename_all = "camelCase")]
114pub enum ProviderStreamEvent {
115    /// Public assistant text that can be rendered immediately.
116    TextDelta {
117        /// Newly received text; it is not the accumulated response.
118        delta: String,
119        /// Complete provider frame that produced the delta.
120        #[serde(default)]
121        opaque: Value,
122    },
123    /// A client-executed tool call whose stable identity and name are known.
124    ToolCallStarted {
125        /// Provider call identifier used for later deltas and results.
126        id: String,
127        /// Registered tool name.
128        name: String,
129        /// Complete provider frame that announced the call.
130        #[serde(default)]
131        opaque: Value,
132    },
133    /// A partial JSON string for one tool call's arguments.
134    ///
135    /// The delta is deliberately not parsed until the provider marks the call
136    /// complete. Partial JSON is not safe to execute.
137    ToolCallArgumentsDelta {
138        /// Provider call identifier.
139        id: String,
140        /// Newly received JSON fragment.
141        delta: String,
142        /// Complete provider frame that produced the fragment.
143        #[serde(default)]
144        opaque: Value,
145    },
146    /// A fully normalized tool call whose arguments are valid JSON.
147    ToolCallCompleted {
148        /// Structured call ready for runtime validation and policy checks.
149        call: ToolCall,
150        /// Complete provider frame that finalized the call.
151        #[serde(default)]
152        opaque: Value,
153    },
154    /// Provider-reported cumulative token accounting.
155    Usage {
156        /// Latest normalized usage counters.
157        usage: TokenUsage,
158        /// Complete provider frame containing the counters.
159        #[serde(default)]
160        opaque: Value,
161    },
162    /// Canonical terminal result for the provider turn.
163    Completed {
164        /// Fully accumulated turn.
165        turn: ProviderTurn,
166    },
167    /// A well-formed provider event with no lossless canonical equivalent.
168    Opaque {
169        /// Provider event name, or `message` when the transport omitted one.
170        event_type: String,
171        /// Complete in-memory provider payload; stripped before persistence.
172        payload: Value,
173    },
174}
175
176/// Provider-neutral stop reasons.
177#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
178#[serde(rename_all = "snake_case")]
179pub enum StopReason {
180    /// Provider returned a final response.
181    EndTurn,
182    /// Provider requested one or more tools.
183    ToolUse,
184    /// Output limit was reached.
185    MaxTokens,
186    /// Provider refused the task.
187    Refusal,
188}
189
190/// Provider token usage.
191#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
192#[serde(rename_all = "camelCase")]
193pub struct TokenUsage {
194    /// Input tokens.
195    pub input_tokens: u64,
196    /// Output tokens.
197    pub output_tokens: u64,
198    /// Reused/cache-hit tokens.
199    pub cached_tokens: u64,
200    /// Provider-reported reasoning tokens, never reasoning content.
201    pub reasoning_tokens: u64,
202}
203
204/// Failures visible at the provider boundary.
205#[derive(Debug, Error)]
206pub enum ProviderError {
207    /// Configuration or authentication is missing.
208    #[error("provider configuration error: {0}")]
209    Configuration(String),
210    /// Requested canonical capability is unsupported.
211    #[error("provider does not support required capability: {0}")]
212    Unsupported(String),
213    /// Network or HTTP failure.
214    #[error("provider transport failed: {0}")]
215    Transport(String),
216    /// Provider returned a structured API error.
217    #[error("provider API error: {0}")]
218    Api(String),
219    /// Response could not be normalized safely.
220    #[error("provider response was invalid: {0}")]
221    InvalidResponse(String),
222    /// Turn was cancelled.
223    #[error("provider turn cancelled")]
224    Cancelled,
225}