Skip to main content

kcode_codex_runtime_v2/
model.rs

1use std::{
2    fmt::{Debug, Formatter},
3    time::Duration,
4};
5
6use serde_json::Value;
7
8use crate::error::{Error, ErrorKind, Result};
9
10/// Model reasoning effort accepted by Codex.
11#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
12pub enum ReasoningEffort {
13    /// Disable reasoning when supported.
14    None,
15    /// Minimal reasoning.
16    Minimal,
17    /// Low reasoning.
18    Low,
19    /// Medium reasoning.
20    Medium,
21    /// High reasoning.
22    High,
23    /// Extra-high reasoning.
24    #[default]
25    XHigh,
26    /// Maximum reasoning when supported.
27    Max,
28}
29
30impl ReasoningEffort {
31    /// Returns the Codex protocol value.
32    pub const fn as_str(self) -> &'static str {
33        match self {
34            Self::None => "none",
35            Self::Minimal => "minimal",
36            Self::Low => "low",
37            Self::Medium => "medium",
38            Self::High => "high",
39            Self::XHigh => "xhigh",
40            Self::Max => "max",
41        }
42    }
43}
44
45/// A supported image media type for an inline image turn.
46#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
47pub enum ImageMediaType {
48    /// Portable Network Graphics.
49    Png,
50    /// JPEG image data.
51    Jpeg,
52    /// WebP image data.
53    Webp,
54    /// Graphics Interchange Format.
55    Gif,
56}
57
58impl ImageMediaType {
59    /// Returns the media type used in the image data URL.
60    pub const fn mime_type(self) -> &'static str {
61        match self {
62            Self::Png => "image/png",
63            Self::Jpeg => "image/jpeg",
64            Self::Webp => "image/webp",
65            Self::Gif => "image/gif",
66        }
67    }
68}
69
70/// One in-memory image supplied to a dedicated image turn.
71#[derive(Clone, Eq, PartialEq)]
72pub struct ImageInput {
73    media_type: ImageMediaType,
74    bytes: Vec<u8>,
75}
76
77impl ImageInput {
78    /// Constructs a nonempty image input.
79    pub fn new(media_type: ImageMediaType, bytes: impl Into<Vec<u8>>) -> Result<Self> {
80        let bytes = bytes.into();
81        if bytes.is_empty() {
82            return Err(Error::new(
83                ErrorKind::InvalidInput,
84                "Codex image input must not be empty",
85            ));
86        }
87        Ok(Self { media_type, bytes })
88    }
89
90    /// Returns the declared image media type.
91    pub const fn media_type(&self) -> ImageMediaType {
92        self.media_type
93    }
94
95    /// Returns the exact image bytes.
96    pub fn bytes(&self) -> &[u8] {
97        &self.bytes
98    }
99}
100
101impl Debug for ImageInput {
102    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
103        formatter
104            .debug_struct("ImageInput")
105            .field("media_type", &self.media_type)
106            .field("byte_len", &self.bytes.len())
107            .finish()
108    }
109}
110
111/// A fresh, ephemeral, tool-free Codex image-analysis turn.
112#[derive(Clone, Debug, PartialEq)]
113pub struct ImageTurnRequest {
114    /// Exact text supplied after the image input items.
115    pub prompt: String,
116    /// Codex model identifier.
117    pub model: String,
118    /// Images supplied in caller order.
119    pub images: Vec<ImageInput>,
120    /// Requested reasoning effort.
121    pub reasoning_effort: ReasoningEffort,
122    /// Maximum duration of the complete turn.
123    pub timeout: Duration,
124}
125
126impl ImageTurnRequest {
127    /// Constructs an image turn using extra-high reasoning and a thirty-minute timeout.
128    pub fn new(
129        prompt: impl Into<String>,
130        model: impl Into<String>,
131        images: Vec<ImageInput>,
132    ) -> Self {
133        Self {
134            prompt: prompt.into(),
135            model: model.into(),
136            images,
137            reasoning_effort: ReasoningEffort::XHigh,
138            timeout: Duration::from_secs(30 * 60),
139        }
140    }
141}
142
143/// A dynamic function exposed to Codex for a fresh thread.
144#[derive(Clone, Debug, PartialEq)]
145pub struct DynamicTool {
146    /// Function name visible to the model.
147    pub name: String,
148    /// Short function description visible to the model.
149    pub description: String,
150    /// JSON Schema for the function arguments.
151    pub input_schema: Value,
152}
153
154impl DynamicTool {
155    /// Constructs one function tool.
156    pub fn new(
157        name: impl Into<String>,
158        description: impl Into<String>,
159        input_schema: Value,
160    ) -> Self {
161        Self {
162            name: name.into(),
163            description: description.into(),
164            input_schema,
165        }
166    }
167}
168
169/// One standard Codex turn request.
170#[derive(Clone, Debug, PartialEq)]
171pub struct AgentRequest {
172    /// Exact text supplied as the user input item for this turn.
173    pub input: String,
174    /// Codex model identifier.
175    pub model: String,
176    /// Requested reasoning effort.
177    pub reasoning_effort: ReasoningEffort,
178    /// Existing Codex thread identifier to resume.
179    pub previous_thread_id: Option<String>,
180    /// Dynamic functions supplied when a fresh thread is created.
181    pub tools: Vec<DynamicTool>,
182    /// Whether a fresh thread avoids persistent Codex session storage.
183    pub ephemeral: bool,
184    /// Maximum duration of the complete turn, including tool handling.
185    pub timeout: Duration,
186}
187
188impl AgentRequest {
189    /// Constructs a fresh turn with no tools.
190    pub fn new(input: impl Into<String>, model: impl Into<String>) -> Self {
191        Self {
192            input: input.into(),
193            model: model.into(),
194            reasoning_effort: ReasoningEffort::XHigh,
195            previous_thread_id: None,
196            tools: Vec::new(),
197            ephemeral: false,
198            timeout: Duration::from_secs(30 * 60),
199        }
200    }
201}
202
203/// A dynamic function call requested by Codex.
204#[derive(Clone, Debug, PartialEq)]
205pub struct DynamicToolCall {
206    /// Protocol call identifier used when returning the result.
207    pub call_id: String,
208    /// Dynamic function name.
209    pub tool: String,
210    /// Parsed JSON arguments supplied by the model.
211    pub arguments: Value,
212}
213
214/// Caller-provided result for one dynamic function call.
215#[derive(Clone, Debug, Eq, PartialEq)]
216pub struct ToolResult {
217    /// Whether the function completed successfully.
218    pub success: bool,
219    /// Text returned to the model.
220    pub text: String,
221}
222
223impl ToolResult {
224    /// Constructs a successful text result.
225    pub fn success(text: impl Into<String>) -> Self {
226        Self {
227            success: true,
228            text: text.into(),
229        }
230    }
231
232    /// Constructs a failed text result for the model.
233    pub fn failure(text: impl Into<String>) -> Self {
234        Self {
235            success: false,
236            text: text.into(),
237        }
238    }
239}
240
241/// Normalized cumulative and latest-turn token accounting.
242#[derive(Clone, Debug, Default, Eq, PartialEq)]
243pub struct TokenUsage {
244    /// Cumulative input tokens for the thread.
245    pub input_tokens: u64,
246    /// Cumulative output tokens for the thread, including reasoning output.
247    pub output_tokens: u64,
248    /// Cumulative cached input tokens for the thread.
249    pub cached_input_tokens: u64,
250    /// Cumulative reasoning output tokens for the thread.
251    pub reasoning_output_tokens: u64,
252    /// Input tokens for the latest model round.
253    pub last_input_tokens: Option<u64>,
254    /// Output tokens for the latest model round.
255    pub last_output_tokens: Option<u64>,
256}
257
258/// Successful terminal state of one Codex turn.
259#[derive(Clone, Debug, Eq, PartialEq)]
260pub struct CompletedTurn {
261    /// Codex thread identifier used for continuation.
262    pub thread_id: String,
263    /// Codex turn identifier.
264    pub turn_id: String,
265    /// Terminal assistant text. It may be empty after a control tool call.
266    pub answer: String,
267    /// Latest provider token accounting, when reported.
268    pub usage: Option<TokenUsage>,
269}
270
271/// Event yielded while a Codex turn is running.
272#[derive(Clone, Debug, PartialEq)]
273pub enum AgentEvent {
274    /// Exact UTF-8 JSONL record written by the client to Codex, including its newline.
275    ProviderInput(String),
276    /// Provider accounting for the latest completed model inference.
277    ///
278    /// `TokenUsage` contains cumulative totals for the provider turn plus the
279    /// latest inference's input/output counts when the provider reports them.
280    UsageUpdated(TokenUsage),
281    /// A dynamic function call requiring a response from the caller.
282    ToolCall(DynamicToolCall),
283    /// The turn completed successfully.
284    Completed(CompletedTurn),
285}