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}