Skip to main content

oharness_core/
completion.rs

1//! Shared completion types (§4.9).
2
3use crate::ids::ModelId;
4use crate::message::{Content, Message};
5use crate::MetadataMap;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
11pub struct CompletionRequest {
12    pub messages: Vec<Message>,
13    #[serde(default, skip_serializing_if = "Vec::is_empty")]
14    pub tools: Vec<ToolSpec>,
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub system: Option<String>,
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub max_tokens: Option<u32>,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub temperature: Option<f32>,
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    pub stop_sequences: Vec<String>,
23    #[serde(default, skip_serializing_if = "CacheHints::is_empty")]
24    pub cache_hints: CacheHints,
25    /// Provider extensions, reverse-DNS-namespaced (e.g. `anthropic.thinking`).
26    #[serde(default, skip_serializing_if = "MetadataMap::is_empty")]
27    pub extensions: MetadataMap,
28}
29
30impl CompletionRequest {
31    pub fn new(messages: Vec<Message>) -> Self {
32        Self {
33            messages,
34            tools: Vec::new(),
35            system: None,
36            max_tokens: None,
37            temperature: None,
38            stop_sequences: Vec::new(),
39            cache_hints: CacheHints::default(),
40            extensions: MetadataMap::new(),
41        }
42    }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
47pub struct CompletionResponse {
48    pub id: String,
49    pub model: ModelId,
50    pub content: Vec<Content>,
51    pub stop_reason: StopReason,
52    pub usage: Usage,
53}
54
55/// Reason a model stopped producing output. `Error(String)` wraps provider-specific
56/// error reasons that don't map to a known variant.
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
58#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
59#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
60pub enum StopReason {
61    EndTurn,
62    MaxTokens,
63    StopSequence(String),
64    ToolUse,
65    Refusal,
66    Error(String),
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
71pub struct ToolSpec {
72    pub name: String,
73    pub description: String,
74    /// JSON Schema for the tool's input. Never transformed — round-trips verbatim.
75    pub input_schema: Value,
76}
77
78#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
80pub struct CacheHints {
81    #[serde(default, skip_serializing_if = "Vec::is_empty")]
82    pub breakpoints: Vec<CacheBreakpoint>,
83}
84
85impl CacheHints {
86    pub fn is_empty(&self) -> bool {
87        self.breakpoints.is_empty()
88    }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
93pub struct CacheBreakpoint {
94    /// Message index at which to mark a cache point (inclusive upper bound of the
95    /// cacheable prefix).
96    pub message_index: usize,
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub ttl: Option<CacheTtl>,
99}
100
101#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
102#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
103#[serde(rename_all = "snake_case")]
104pub enum CacheTtl {
105    Short,
106    Long,
107}
108
109/// Token accounting for a single completion. Fields default to zero so providers
110/// that don't report cache figures can leave them untouched.
111#[derive(Debug, Clone, Default, Serialize, Deserialize)]
112#[cfg_attr(feature = "schemars-export", derive(schemars::JsonSchema))]
113pub struct Usage {
114    pub tokens_input: u64,
115    pub tokens_output: u64,
116    #[serde(default)]
117    pub tokens_cache_read: u64,
118    #[serde(default)]
119    pub tokens_cache_create: u64,
120}
121
122impl Usage {
123    pub fn add(&mut self, other: &Usage) {
124        self.tokens_input += other.tokens_input;
125        self.tokens_output += other.tokens_output;
126        self.tokens_cache_read += other.tokens_cache_read;
127        self.tokens_cache_create += other.tokens_cache_create;
128    }
129}