codex_runtime/runtime/api/
models.rs1use std::time::Duration;
2
3use serde_json::Value;
4use thiserror::Error;
5use tokio::sync::broadcast::Receiver as BroadcastReceiver;
6use tokio::time::Instant;
7
8use crate::plugin::{BlockReason, HookPhase};
9use crate::runtime::core::Runtime;
10use crate::runtime::errors::{RpcError, RuntimeError};
11use crate::runtime::events::{
12 AgentMessageDeltaNotification, Envelope, TurnCancelledNotification, TurnCompletedNotification,
13 TurnFailedNotification, TurnInterruptedNotification,
14};
15use crate::runtime::hooks::RuntimeHookConfig;
16use crate::runtime::turn_lifecycle::LaggedTurnTerminal;
17use crate::runtime::turn_output::TurnStreamCollector;
18
19use super::{
20 flow::HookExecutionState, turn_error::PromptTurnErrorSignal, ApprovalPolicy, PromptAttachment,
21 ReasoningEffort, SandboxPolicy, ThreadId, TurnId, DEFAULT_REASONING_EFFORT,
22};
23
24#[derive(Clone, Debug, PartialEq)]
25pub struct PromptRunParams {
26 pub cwd: String,
27 pub prompt: String,
28 pub model: Option<String>,
29 pub effort: Option<ReasoningEffort>,
30 pub approval_policy: ApprovalPolicy,
31 pub sandbox_policy: SandboxPolicy,
32 pub privileged_escalation_approved: bool,
35 pub attachments: Vec<PromptAttachment>,
36 pub timeout: Duration,
37 pub output_schema: Option<Value>,
38}
39
40impl PromptRunParams {
41 pub fn new(cwd: impl Into<String>, prompt: impl Into<String>) -> Self {
44 Self {
45 cwd: cwd.into(),
46 prompt: prompt.into(),
47 model: None,
48 effort: Some(DEFAULT_REASONING_EFFORT),
49 approval_policy: ApprovalPolicy::Never,
50 sandbox_policy: SandboxPolicy::Preset(super::SandboxPreset::ReadOnly),
51 privileged_escalation_approved: false,
52 attachments: Vec::new(),
53 timeout: Duration::from_secs(120),
54 output_schema: None,
55 }
56 }
57
58 pub fn with_model(mut self, model: impl Into<String>) -> Self {
61 self.model = Some(model.into());
62 self
63 }
64
65 pub fn with_effort(mut self, effort: ReasoningEffort) -> Self {
68 self.effort = Some(effort);
69 self
70 }
71
72 pub fn with_approval_policy(mut self, approval_policy: ApprovalPolicy) -> Self {
75 self.approval_policy = approval_policy;
76 self
77 }
78
79 pub fn with_sandbox_policy(mut self, sandbox_policy: SandboxPolicy) -> Self {
82 self.sandbox_policy = sandbox_policy;
83 self
84 }
85
86 pub fn allow_privileged_escalation(mut self) -> Self {
89 self.privileged_escalation_approved = true;
90 self
91 }
92
93 pub fn with_timeout(mut self, timeout: Duration) -> Self {
96 self.timeout = timeout;
97 self
98 }
99
100 pub fn with_output_schema(mut self, output_schema: Value) -> Self {
102 self.output_schema = Some(output_schema);
103 self
104 }
105
106 pub fn with_attachment(mut self, attachment: PromptAttachment) -> Self {
109 self.attachments.push(attachment);
110 self
111 }
112
113 pub fn attach_path(self, path: impl Into<String>) -> Self {
116 self.with_attachment(PromptAttachment::AtPath {
117 path: path.into(),
118 placeholder: None,
119 })
120 }
121
122 pub fn attach_path_with_placeholder(
125 self,
126 path: impl Into<String>,
127 placeholder: impl Into<String>,
128 ) -> Self {
129 self.with_attachment(PromptAttachment::AtPath {
130 path: path.into(),
131 placeholder: Some(placeholder.into()),
132 })
133 }
134
135 pub fn attach_image_url(self, url: impl Into<String>) -> Self {
138 self.with_attachment(PromptAttachment::ImageUrl { url: url.into() })
139 }
140
141 pub fn attach_local_image(self, path: impl Into<String>) -> Self {
144 self.with_attachment(PromptAttachment::LocalImage { path: path.into() })
145 }
146
147 pub fn attach_skill(self, name: impl Into<String>, path: impl Into<String>) -> Self {
150 self.with_attachment(PromptAttachment::Skill {
151 name: name.into(),
152 path: path.into(),
153 })
154 }
155}
156
157#[derive(Clone, Debug, PartialEq, Eq)]
158pub struct PromptRunResult {
159 pub thread_id: ThreadId,
160 pub turn_id: TurnId,
161 pub assistant_text: String,
162}
163
164#[derive(Clone, Debug, PartialEq)]
165pub enum PromptRunStreamEvent {
166 AgentMessageDelta(AgentMessageDeltaNotification),
167 TurnCompleted(TurnCompletedNotification),
168 TurnFailed(TurnFailedNotification),
169 TurnInterrupted(TurnInterruptedNotification),
170 TurnCancelled(TurnCancelledNotification),
171}
172
173pub(crate) struct PromptRunStreamState {
174 pub(crate) last_turn_error: Option<PromptTurnErrorSignal>,
175 pub(crate) lagged_terminal: Option<LaggedTurnTerminal>,
176 pub(crate) final_result: Option<Result<PromptRunResult, PromptRunError>>,
177}
178
179pub(crate) struct PromptStreamCleanupState {
180 pub(crate) run_cwd: String,
181 pub(crate) run_model: Option<String>,
182 pub(crate) scoped_hooks: Option<RuntimeHookConfig>,
183 pub(crate) hook_state: Option<HookExecutionState>,
184 pub(crate) cleaned_up: bool,
185}
186
187pub struct PromptRunStream {
188 pub(crate) runtime: Runtime,
189 pub(crate) thread_id: ThreadId,
190 pub(crate) turn_id: TurnId,
191 pub(crate) live_rx: BroadcastReceiver<Envelope>,
192 pub(crate) stream: TurnStreamCollector,
193 pub(crate) state: PromptRunStreamState,
194 pub(crate) deadline: Instant,
195 pub(crate) timeout: Duration,
196 pub(crate) cleanup: PromptStreamCleanupState,
197}
198
199#[derive(Clone, Copy, Debug, PartialEq, Eq)]
200pub enum PromptTurnTerminalState {
201 Failed,
202 CompletedWithoutAssistantText,
203}
204
205#[derive(Clone, Debug, PartialEq, Eq)]
206pub struct PromptTurnFailure {
207 pub terminal_state: PromptTurnTerminalState,
208 pub source_method: String,
209 pub code: Option<i64>,
210 pub message: String,
211}
212
213impl std::fmt::Display for PromptTurnFailure {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 let terminal = match self.terminal_state {
216 PromptTurnTerminalState::Failed => "failed",
217 PromptTurnTerminalState::CompletedWithoutAssistantText => {
218 "completed_without_assistant_text"
219 }
220 };
221 if let Some(code) = self.code {
222 write!(
223 f,
224 "terminal={terminal} source_method={} code={code} message={}",
225 self.source_method, self.message
226 )
227 } else {
228 write!(
229 f,
230 "terminal={terminal} source_method={} message={}",
231 self.source_method, self.message
232 )
233 }
234 }
235}
236
237#[derive(Clone, Debug, Error, PartialEq, Eq)]
238pub enum PromptRunError {
239 #[error("rpc error: {0}")]
240 Rpc(#[from] RpcError),
241 #[error("runtime error: {0}")]
242 Runtime(#[from] RuntimeError),
243 #[error("turn failed: {0}")]
244 TurnFailedWithContext(PromptTurnFailure),
245 #[error("turn failed")]
246 TurnFailed,
247 #[error("turn interrupted")]
248 TurnInterrupted,
249 #[error("turn timed out after {0:?}")]
250 Timeout(Duration),
251 #[error("turn completed without assistant text: {0}")]
252 TurnCompletedWithoutAssistantText(PromptTurnFailure),
253 #[error("assistant text is empty")]
254 EmptyAssistantText,
255 #[error("attachment not found: {0}")]
256 AttachmentNotFound(String),
257 #[error("blocked by hook '{hook_name}' at {phase:?}: {message}")]
259 BlockedByHook {
260 hook_name: String,
261 phase: HookPhase,
262 message: String,
263 },
264}
265
266impl PromptRunError {
267 pub(crate) fn from_block(r: BlockReason) -> Self {
270 Self::BlockedByHook {
271 hook_name: r.hook_name,
272 phase: r.phase,
273 message: r.message,
274 }
275 }
276}