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}