proofborne-core 0.2.0

Versioned contracts, events, provider types, and proof graph for Proofborne
Documentation
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
use uuid::Uuid;

use crate::{ToolCall, ToolDefinition, ToolResult};

/// Capabilities declared by a provider adapter.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ProviderCapabilities {
    /// Token-by-token or event streaming.
    pub streaming: bool,
    /// Client-executed function calls.
    pub tools: bool,
    /// JSON-schema-constrained model output.
    pub structured_output: bool,
    /// Non-text input.
    pub multimodal_input: bool,
    /// Known context limit, absent when discoverable only per model.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context_tokens: Option<u64>,
}

/// Provider-neutral conversation items retained by the session engine.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ConversationItem {
    /// User-authored text.
    UserText {
        /// Message body.
        text: String,
    },
    /// Model-authored public text.
    AssistantText {
        /// Message body.
        text: String,
    },
    /// Model-requested tool execution.
    ToolCall {
        /// Structured call.
        call: ToolCall,
    },
    /// Runtime tool result.
    ToolResult {
        /// Structured result.
        result: ToolResult,
    },
}

/// One provider turn request.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ProviderRequest {
    /// Proofborne session identifier.
    pub session_id: Uuid,
    /// Provider model selector; never silently replaced.
    pub model: String,
    /// Stable runtime instructions.
    pub instructions: String,
    /// Provider-neutral history.
    pub items: Vec<ConversationItem>,
    /// Tools available for this turn.
    #[serde(default)]
    pub tools: Vec<ToolDefinition>,
    /// Optional JSON Schema that the provider must enforce for assistant text.
    ///
    /// Adapters that do not support schema-constrained output must return
    /// [`ProviderError::Unsupported`] instead of silently treating this as a
    /// plain-text request.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub output_schema: Option<Value>,
    /// Optional previous provider response handle.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub previous_response_id: Option<String>,
}

/// Normalized result of one provider turn.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ProviderTurn {
    /// Adapter identifier.
    pub provider: String,
    /// Actual model reported by the provider.
    pub model: String,
    /// In-memory provider response handle; runtimes must not persist the raw value.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_id: Option<String>,
    /// Public model text; hidden reasoning is never requested.
    #[serde(default)]
    pub output_text: String,
    /// Structured client tool calls.
    #[serde(default)]
    pub tool_calls: Vec<ToolCall>,
    /// Why the turn ended.
    pub stop_reason: StopReason,
    /// Token accounting when supplied.
    #[serde(default)]
    pub usage: TokenUsage,
    /// Lossless in-memory provider payload for fields outside the canonical model.
    ///
    /// Runtimes must remove this field before persisting events or proof material.
    #[serde(default)]
    pub opaque: Value,
}

/// Provider-neutral events emitted while one model turn is in progress.
///
/// Adapters retain complete provider frames in `opaque` while normalizing a live
/// stream. Consumers may inspect that in-memory value, but runtimes must strip it
/// before persisting events, checkpoints, proof bundles, or generated reports.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ProviderStreamEvent {
    /// Public assistant text that can be rendered immediately.
    TextDelta {
        /// Newly received text; it is not the accumulated response.
        delta: String,
        /// Complete provider frame that produced the delta.
        #[serde(default)]
        opaque: Value,
    },
    /// A client-executed tool call whose stable identity and name are known.
    ToolCallStarted {
        /// Provider call identifier used for later deltas and results.
        id: String,
        /// Registered tool name.
        name: String,
        /// Complete provider frame that announced the call.
        #[serde(default)]
        opaque: Value,
    },
    /// A partial JSON string for one tool call's arguments.
    ///
    /// The delta is deliberately not parsed until the provider marks the call
    /// complete. Partial JSON is not safe to execute.
    ToolCallArgumentsDelta {
        /// Provider call identifier.
        id: String,
        /// Newly received JSON fragment.
        delta: String,
        /// Complete provider frame that produced the fragment.
        #[serde(default)]
        opaque: Value,
    },
    /// A fully normalized tool call whose arguments are valid JSON.
    ToolCallCompleted {
        /// Structured call ready for runtime validation and policy checks.
        call: ToolCall,
        /// Complete provider frame that finalized the call.
        #[serde(default)]
        opaque: Value,
    },
    /// Provider-reported cumulative token accounting.
    Usage {
        /// Latest normalized usage counters.
        usage: TokenUsage,
        /// Complete provider frame containing the counters.
        #[serde(default)]
        opaque: Value,
    },
    /// Canonical terminal result for the provider turn.
    Completed {
        /// Fully accumulated turn.
        turn: ProviderTurn,
    },
    /// A well-formed provider event with no lossless canonical equivalent.
    Opaque {
        /// Provider event name, or `message` when the transport omitted one.
        event_type: String,
        /// Complete in-memory provider payload; stripped before persistence.
        payload: Value,
    },
}

/// Provider-neutral stop reasons.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum StopReason {
    /// Provider returned a final response.
    EndTurn,
    /// Provider requested one or more tools.
    ToolUse,
    /// Output limit was reached.
    MaxTokens,
    /// Provider refused the task.
    Refusal,
}

/// Provider token usage.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TokenUsage {
    /// Input tokens.
    pub input_tokens: u64,
    /// Output tokens.
    pub output_tokens: u64,
    /// Reused/cache-hit tokens.
    pub cached_tokens: u64,
    /// Provider-reported reasoning tokens, never reasoning content.
    pub reasoning_tokens: u64,
}

/// Failures visible at the provider boundary.
#[derive(Debug, Error)]
pub enum ProviderError {
    /// Configuration or authentication is missing.
    #[error("provider configuration error: {0}")]
    Configuration(String),
    /// Requested canonical capability is unsupported.
    #[error("provider does not support required capability: {0}")]
    Unsupported(String),
    /// Network or HTTP failure.
    #[error("provider transport failed: {0}")]
    Transport(String),
    /// Provider returned a structured API error.
    #[error("provider API error: {0}")]
    Api(String),
    /// Response could not be normalized safely.
    #[error("provider response was invalid: {0}")]
    InvalidResponse(String),
    /// Turn was cancelled.
    #[error("provider turn cancelled")]
    Cancelled,
}