1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use crate::protocol::common::AuthMode;
5use codex_protocol::ConversationId;
6use codex_protocol::account::PlanType;
7use codex_protocol::approvals::SandboxCommandAssessment as CoreSandboxCommandAssessment;
8use codex_protocol::config_types::ReasoningEffort;
9use codex_protocol::config_types::ReasoningSummary;
10use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;
11use codex_protocol::items::TurnItem as CoreTurnItem;
12use codex_protocol::models::ResponseItem;
13use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand;
14use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
15use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
16use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
17use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
18use codex_protocol::user_input::UserInput as CoreUserInput;
19use mcp_types::ContentBlock as McpContentBlock;
20use schemars::JsonSchema;
21use serde::Deserialize;
22use serde::Serialize;
23use serde_json::Value as JsonValue;
24use thiserror::Error;
25use ts_rs::TS;
26
27macro_rules! v2_enum_from_core {
30 (
31 pub enum $Name:ident from $Src:path { $( $Variant:ident ),+ $(,)? }
32 ) => {
33 #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
34 #[serde(rename_all = "camelCase")]
35 #[ts(export_to = "v2/")]
36 pub enum $Name { $( $Variant ),+ }
37
38 impl $Name {
39 pub fn to_core(self) -> $Src {
40 match self { $( $Name::$Variant => <$Src>::$Variant ),+ }
41 }
42 }
43
44 impl From<$Src> for $Name {
45 fn from(value: $Src) -> Self {
46 match value { $( <$Src>::$Variant => $Name::$Variant ),+ }
47 }
48 }
49 };
50}
51
52#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
57#[serde(rename_all = "camelCase")]
58#[ts(export_to = "v2/")]
59pub enum CodexErrorInfo {
60 ContextWindowExceeded,
61 UsageLimitExceeded,
62 HttpConnectionFailed {
63 #[serde(rename = "httpStatusCode")]
64 #[ts(rename = "httpStatusCode")]
65 http_status_code: Option<u16>,
66 },
67 ResponseStreamConnectionFailed {
69 #[serde(rename = "httpStatusCode")]
70 #[ts(rename = "httpStatusCode")]
71 http_status_code: Option<u16>,
72 },
73 InternalServerError,
74 Unauthorized,
75 BadRequest,
76 SandboxError,
77 ResponseStreamDisconnected {
79 #[serde(rename = "httpStatusCode")]
80 #[ts(rename = "httpStatusCode")]
81 http_status_code: Option<u16>,
82 },
83 ResponseTooManyFailedAttempts {
85 #[serde(rename = "httpStatusCode")]
86 #[ts(rename = "httpStatusCode")]
87 http_status_code: Option<u16>,
88 },
89 Other,
90}
91
92impl From<CoreCodexErrorInfo> for CodexErrorInfo {
93 fn from(value: CoreCodexErrorInfo) -> Self {
94 match value {
95 CoreCodexErrorInfo::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
96 CoreCodexErrorInfo::UsageLimitExceeded => CodexErrorInfo::UsageLimitExceeded,
97 CoreCodexErrorInfo::HttpConnectionFailed { http_status_code } => {
98 CodexErrorInfo::HttpConnectionFailed { http_status_code }
99 }
100 CoreCodexErrorInfo::ResponseStreamConnectionFailed { http_status_code } => {
101 CodexErrorInfo::ResponseStreamConnectionFailed { http_status_code }
102 }
103 CoreCodexErrorInfo::InternalServerError => CodexErrorInfo::InternalServerError,
104 CoreCodexErrorInfo::Unauthorized => CodexErrorInfo::Unauthorized,
105 CoreCodexErrorInfo::BadRequest => CodexErrorInfo::BadRequest,
106 CoreCodexErrorInfo::SandboxError => CodexErrorInfo::SandboxError,
107 CoreCodexErrorInfo::ResponseStreamDisconnected { http_status_code } => {
108 CodexErrorInfo::ResponseStreamDisconnected { http_status_code }
109 }
110 CoreCodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code } => {
111 CodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code }
112 }
113 CoreCodexErrorInfo::Other => CodexErrorInfo::Other,
114 }
115 }
116}
117
118v2_enum_from_core!(
119 pub enum AskForApproval from codex_protocol::protocol::AskForApproval {
120 UnlessTrusted, OnFailure, OnRequest, Never
121 }
122);
123
124v2_enum_from_core!(
125 pub enum SandboxMode from codex_protocol::config_types::SandboxMode {
126 ReadOnly, WorkspaceWrite, DangerFullAccess
127 }
128);
129
130v2_enum_from_core!(
131 pub enum CommandRiskLevel from codex_protocol::approvals::SandboxRiskLevel {
132 Low,
133 Medium,
134 High
135 }
136);
137
138#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
139#[serde(rename_all = "camelCase")]
140#[ts(export_to = "v2/")]
141pub enum ApprovalDecision {
142 Accept,
143 Decline,
144 Cancel,
145}
146
147#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
148#[serde(tag = "type", rename_all = "camelCase")]
149#[ts(tag = "type")]
150#[ts(export_to = "v2/")]
151pub enum SandboxPolicy {
152 DangerFullAccess,
153 ReadOnly,
154 #[serde(rename_all = "camelCase")]
155 #[ts(rename_all = "camelCase")]
156 WorkspaceWrite {
157 #[serde(default)]
158 writable_roots: Vec<PathBuf>,
159 #[serde(default)]
160 network_access: bool,
161 #[serde(default)]
162 exclude_tmpdir_env_var: bool,
163 #[serde(default)]
164 exclude_slash_tmp: bool,
165 },
166}
167
168impl SandboxPolicy {
169 pub fn to_core(&self) -> codex_protocol::protocol::SandboxPolicy {
170 match self {
171 SandboxPolicy::DangerFullAccess => {
172 codex_protocol::protocol::SandboxPolicy::DangerFullAccess
173 }
174 SandboxPolicy::ReadOnly => codex_protocol::protocol::SandboxPolicy::ReadOnly,
175 SandboxPolicy::WorkspaceWrite {
176 writable_roots,
177 network_access,
178 exclude_tmpdir_env_var,
179 exclude_slash_tmp,
180 } => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
181 writable_roots: writable_roots.clone(),
182 network_access: *network_access,
183 exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
184 exclude_slash_tmp: *exclude_slash_tmp,
185 },
186 }
187 }
188}
189
190impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
191 fn from(value: codex_protocol::protocol::SandboxPolicy) -> Self {
192 match value {
193 codex_protocol::protocol::SandboxPolicy::DangerFullAccess => {
194 SandboxPolicy::DangerFullAccess
195 }
196 codex_protocol::protocol::SandboxPolicy::ReadOnly => SandboxPolicy::ReadOnly,
197 codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
198 writable_roots,
199 network_access,
200 exclude_tmpdir_env_var,
201 exclude_slash_tmp,
202 } => SandboxPolicy::WorkspaceWrite {
203 writable_roots,
204 network_access,
205 exclude_tmpdir_env_var,
206 exclude_slash_tmp,
207 },
208 }
209 }
210}
211
212#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
213#[serde(rename_all = "camelCase")]
214#[ts(export_to = "v2/")]
215pub struct SandboxCommandAssessment {
216 pub description: String,
217 pub risk_level: CommandRiskLevel,
218}
219
220impl SandboxCommandAssessment {
221 pub fn into_core(self) -> CoreSandboxCommandAssessment {
222 CoreSandboxCommandAssessment {
223 description: self.description,
224 risk_level: self.risk_level.to_core(),
225 }
226 }
227}
228
229impl From<CoreSandboxCommandAssessment> for SandboxCommandAssessment {
230 fn from(value: CoreSandboxCommandAssessment) -> Self {
231 Self {
232 description: value.description,
233 risk_level: CommandRiskLevel::from(value.risk_level),
234 }
235 }
236}
237
238#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
239#[serde(tag = "type", rename_all = "camelCase")]
240#[ts(tag = "type")]
241#[ts(export_to = "v2/")]
242pub enum CommandAction {
243 Read {
244 command: String,
245 name: String,
246 path: PathBuf,
247 },
248 ListFiles {
249 command: String,
250 path: Option<String>,
251 },
252 Search {
253 command: String,
254 query: Option<String>,
255 path: Option<String>,
256 },
257 Unknown {
258 command: String,
259 },
260}
261
262impl CommandAction {
263 pub fn into_core(self) -> CoreParsedCommand {
264 match self {
265 CommandAction::Read {
266 command: cmd,
267 name,
268 path,
269 } => CoreParsedCommand::Read { cmd, name, path },
270 CommandAction::ListFiles { command: cmd, path } => {
271 CoreParsedCommand::ListFiles { cmd, path }
272 }
273 CommandAction::Search {
274 command: cmd,
275 query,
276 path,
277 } => CoreParsedCommand::Search { cmd, query, path },
278 CommandAction::Unknown { command: cmd } => CoreParsedCommand::Unknown { cmd },
279 }
280 }
281}
282
283impl From<CoreParsedCommand> for CommandAction {
284 fn from(value: CoreParsedCommand) -> Self {
285 match value {
286 CoreParsedCommand::Read { cmd, name, path } => CommandAction::Read {
287 command: cmd,
288 name,
289 path,
290 },
291 CoreParsedCommand::ListFiles { cmd, path } => {
292 CommandAction::ListFiles { command: cmd, path }
293 }
294 CoreParsedCommand::Search { cmd, query, path } => CommandAction::Search {
295 command: cmd,
296 query,
297 path,
298 },
299 CoreParsedCommand::Unknown { cmd } => CommandAction::Unknown { command: cmd },
300 }
301 }
302}
303
304#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
305#[serde(tag = "type", rename_all = "camelCase")]
306#[ts(tag = "type")]
307#[ts(export_to = "v2/")]
308pub enum Account {
309 #[serde(rename = "apiKey", rename_all = "camelCase")]
310 #[ts(rename = "apiKey", rename_all = "camelCase")]
311 ApiKey {},
312
313 #[serde(rename = "chatgpt", rename_all = "camelCase")]
314 #[ts(rename = "chatgpt", rename_all = "camelCase")]
315 Chatgpt { email: String, plan_type: PlanType },
316}
317
318#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
319#[serde(tag = "type")]
320#[ts(tag = "type")]
321#[ts(export_to = "v2/")]
322pub enum LoginAccountParams {
323 #[serde(rename = "apiKey", rename_all = "camelCase")]
324 #[ts(rename = "apiKey", rename_all = "camelCase")]
325 ApiKey {
326 #[serde(rename = "apiKey")]
327 #[ts(rename = "apiKey")]
328 api_key: String,
329 },
330 #[serde(rename = "chatgpt")]
331 #[ts(rename = "chatgpt")]
332 Chatgpt,
333}
334
335#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
336#[serde(tag = "type", rename_all = "camelCase")]
337#[ts(tag = "type")]
338#[ts(export_to = "v2/")]
339pub enum LoginAccountResponse {
340 #[serde(rename = "apiKey", rename_all = "camelCase")]
341 #[ts(rename = "apiKey", rename_all = "camelCase")]
342 ApiKey {},
343 #[serde(rename = "chatgpt", rename_all = "camelCase")]
344 #[ts(rename = "chatgpt", rename_all = "camelCase")]
345 Chatgpt {
346 login_id: String,
349 auth_url: String,
351 },
352}
353
354#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
355#[serde(rename_all = "camelCase")]
356#[ts(export_to = "v2/")]
357pub struct CancelLoginAccountParams {
358 pub login_id: String,
359}
360
361#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
362#[serde(rename_all = "camelCase")]
363#[ts(export_to = "v2/")]
364pub struct CancelLoginAccountResponse {}
365
366#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
367#[serde(rename_all = "camelCase")]
368#[ts(export_to = "v2/")]
369pub struct LogoutAccountResponse {}
370
371#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
372#[serde(rename_all = "camelCase")]
373#[ts(export_to = "v2/")]
374pub struct GetAccountRateLimitsResponse {
375 pub rate_limits: RateLimitSnapshot,
376}
377
378#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
379#[serde(rename_all = "camelCase")]
380#[ts(export_to = "v2/")]
381pub struct GetAccountParams {
382 #[serde(default)]
383 pub refresh_token: bool,
384}
385
386#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
387#[serde(rename_all = "camelCase")]
388#[ts(export_to = "v2/")]
389pub struct GetAccountResponse {
390 pub account: Option<Account>,
391 pub requires_openai_auth: bool,
392}
393
394#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
395#[serde(rename_all = "camelCase")]
396#[ts(export_to = "v2/")]
397pub struct ModelListParams {
398 pub cursor: Option<String>,
400 pub limit: Option<u32>,
402}
403
404#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
405#[serde(rename_all = "camelCase")]
406#[ts(export_to = "v2/")]
407pub struct Model {
408 pub id: String,
409 pub model: String,
410 pub display_name: String,
411 pub description: String,
412 pub supported_reasoning_efforts: Vec<ReasoningEffortOption>,
413 pub default_reasoning_effort: ReasoningEffort,
414 pub is_default: bool,
416}
417
418#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
419#[serde(rename_all = "camelCase")]
420#[ts(export_to = "v2/")]
421pub struct ReasoningEffortOption {
422 pub reasoning_effort: ReasoningEffort,
423 pub description: String,
424}
425
426#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
427#[serde(rename_all = "camelCase")]
428#[ts(export_to = "v2/")]
429pub struct ModelListResponse {
430 pub data: Vec<Model>,
431 pub next_cursor: Option<String>,
434}
435
436#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
437#[serde(rename_all = "camelCase")]
438#[ts(export_to = "v2/")]
439pub struct FeedbackUploadParams {
440 pub classification: String,
441 pub reason: Option<String>,
442 pub conversation_id: Option<ConversationId>,
443 pub include_logs: bool,
444}
445
446#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
447#[serde(rename_all = "camelCase")]
448#[ts(export_to = "v2/")]
449pub struct FeedbackUploadResponse {
450 pub thread_id: String,
451}
452
453#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
456#[serde(rename_all = "camelCase")]
457#[ts(export_to = "v2/")]
458pub struct ThreadStartParams {
459 pub model: Option<String>,
460 pub model_provider: Option<String>,
461 pub cwd: Option<String>,
462 pub approval_policy: Option<AskForApproval>,
463 pub sandbox: Option<SandboxMode>,
464 pub config: Option<HashMap<String, JsonValue>>,
465 pub base_instructions: Option<String>,
466 pub developer_instructions: Option<String>,
467}
468
469#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
470#[serde(rename_all = "camelCase")]
471#[ts(export_to = "v2/")]
472pub struct ThreadStartResponse {
473 pub thread: Thread,
474 pub model: String,
475 pub model_provider: String,
476 pub cwd: PathBuf,
477 pub approval_policy: AskForApproval,
478 pub sandbox: SandboxPolicy,
479 pub reasoning_effort: Option<ReasoningEffort>,
480}
481
482#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
483#[serde(rename_all = "camelCase")]
484#[ts(export_to = "v2/")]
485pub struct ThreadResumeParams {
495 pub thread_id: String,
496
497 pub history: Option<Vec<ResponseItem>>,
501
502 pub path: Option<PathBuf>,
505
506 pub model: Option<String>,
508 pub model_provider: Option<String>,
509 pub cwd: Option<String>,
510 pub approval_policy: Option<AskForApproval>,
511 pub sandbox: Option<SandboxMode>,
512 pub config: Option<HashMap<String, serde_json::Value>>,
513 pub base_instructions: Option<String>,
514 pub developer_instructions: Option<String>,
515}
516
517#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
518#[serde(rename_all = "camelCase")]
519#[ts(export_to = "v2/")]
520pub struct ThreadResumeResponse {
521 pub thread: Thread,
522 pub model: String,
523 pub model_provider: String,
524 pub cwd: PathBuf,
525 pub approval_policy: AskForApproval,
526 pub sandbox: SandboxPolicy,
527 pub reasoning_effort: Option<ReasoningEffort>,
528}
529
530#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
531#[serde(rename_all = "camelCase")]
532#[ts(export_to = "v2/")]
533pub struct ThreadArchiveParams {
534 pub thread_id: String,
535}
536
537#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
538#[serde(rename_all = "camelCase")]
539#[ts(export_to = "v2/")]
540pub struct ThreadArchiveResponse {}
541
542#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
543#[serde(rename_all = "camelCase")]
544#[ts(export_to = "v2/")]
545pub struct ThreadListParams {
546 pub cursor: Option<String>,
548 pub limit: Option<u32>,
550 pub model_providers: Option<Vec<String>>,
553}
554
555#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
556#[serde(rename_all = "camelCase")]
557#[ts(export_to = "v2/")]
558pub struct ThreadListResponse {
559 pub data: Vec<Thread>,
560 pub next_cursor: Option<String>,
563}
564
565#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
566#[serde(rename_all = "camelCase")]
567#[ts(export_to = "v2/")]
568pub struct ThreadCompactParams {
569 pub thread_id: String,
570}
571
572#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
573#[serde(rename_all = "camelCase")]
574#[ts(export_to = "v2/")]
575pub struct ThreadCompactResponse {}
576
577#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
578#[serde(rename_all = "camelCase")]
579#[ts(export_to = "v2/")]
580pub struct Thread {
581 pub id: String,
582 pub preview: String,
584 pub model_provider: String,
585 pub created_at: i64,
587 pub path: PathBuf,
589 pub turns: Vec<Turn>,
593}
594
595#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
596#[serde(rename_all = "camelCase")]
597#[ts(export_to = "v2/")]
598pub struct AccountUpdatedNotification {
599 pub auth_mode: Option<AuthMode>,
600}
601
602#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
603#[serde(rename_all = "camelCase")]
604#[ts(export_to = "v2/")]
605pub struct Turn {
606 pub id: String,
607 pub items: Vec<ThreadItem>,
611 #[serde(flatten)]
612 pub status: TurnStatus,
613}
614
615#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)]
616#[serde(rename_all = "camelCase")]
617#[ts(export_to = "v2/")]
618#[error("{message}")]
619pub struct TurnError {
620 pub message: String,
621 pub codex_error_info: Option<CodexErrorInfo>,
622}
623
624#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
625#[serde(rename_all = "camelCase")]
626#[ts(export_to = "v2/")]
627pub struct ErrorNotification {
628 pub error: TurnError,
629}
630
631#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
632#[serde(tag = "status", rename_all = "camelCase")]
633#[ts(tag = "status", export_to = "v2/")]
634pub enum TurnStatus {
635 Completed,
636 Interrupted,
637 Failed { error: TurnError },
638 InProgress,
639}
640
641#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
643#[serde(rename_all = "camelCase")]
644#[ts(export_to = "v2/")]
645pub struct TurnStartParams {
646 pub thread_id: String,
647 pub input: Vec<UserInput>,
648 pub cwd: Option<PathBuf>,
650 pub approval_policy: Option<AskForApproval>,
652 pub sandbox_policy: Option<SandboxPolicy>,
654 pub model: Option<String>,
656 pub effort: Option<ReasoningEffort>,
658 pub summary: Option<ReasoningSummary>,
660}
661
662#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
663#[serde(rename_all = "camelCase")]
664#[ts(export_to = "v2/")]
665pub struct ReviewStartParams {
666 pub thread_id: String,
667 pub target: ReviewTarget,
668
669 #[serde(default)]
671 pub append_to_original_thread: bool,
672}
673
674#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
675#[serde(tag = "type", rename_all = "camelCase")]
676#[ts(tag = "type", export_to = "v2/")]
677pub enum ReviewTarget {
678 UncommittedChanges,
680
681 #[serde(rename_all = "camelCase")]
683 #[ts(rename_all = "camelCase")]
684 BaseBranch { branch: String },
685
686 #[serde(rename_all = "camelCase")]
688 #[ts(rename_all = "camelCase")]
689 Commit {
690 sha: String,
691 title: Option<String>,
693 },
694
695 #[serde(rename_all = "camelCase")]
697 #[ts(rename_all = "camelCase")]
698 Custom { instructions: String },
699}
700
701#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
702#[serde(rename_all = "camelCase")]
703#[ts(export_to = "v2/")]
704pub struct TurnStartResponse {
705 pub turn: Turn,
706}
707
708#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
709#[serde(rename_all = "camelCase")]
710#[ts(export_to = "v2/")]
711pub struct TurnInterruptParams {
712 pub thread_id: String,
713 pub turn_id: String,
714}
715
716#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
717#[serde(rename_all = "camelCase")]
718#[ts(export_to = "v2/")]
719pub struct TurnInterruptResponse {}
720
721#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
723#[serde(tag = "type", rename_all = "camelCase")]
724#[ts(tag = "type")]
725#[ts(export_to = "v2/")]
726pub enum UserInput {
727 Text { text: String },
728 Image { url: String },
729 LocalImage { path: PathBuf },
730}
731
732impl UserInput {
733 pub fn into_core(self) -> CoreUserInput {
734 match self {
735 UserInput::Text { text } => CoreUserInput::Text { text },
736 UserInput::Image { url } => CoreUserInput::Image { image_url: url },
737 UserInput::LocalImage { path } => CoreUserInput::LocalImage { path },
738 }
739 }
740}
741
742impl From<CoreUserInput> for UserInput {
743 fn from(value: CoreUserInput) -> Self {
744 match value {
745 CoreUserInput::Text { text } => UserInput::Text { text },
746 CoreUserInput::Image { image_url } => UserInput::Image { url: image_url },
747 CoreUserInput::LocalImage { path } => UserInput::LocalImage { path },
748 _ => unreachable!("unsupported user input variant"),
749 }
750 }
751}
752
753#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
754#[serde(tag = "type", rename_all = "camelCase")]
755#[ts(tag = "type")]
756#[ts(export_to = "v2/")]
757pub enum ThreadItem {
758 #[serde(rename_all = "camelCase")]
759 #[ts(rename_all = "camelCase")]
760 UserMessage { id: String, content: Vec<UserInput> },
761 #[serde(rename_all = "camelCase")]
762 #[ts(rename_all = "camelCase")]
763 AgentMessage { id: String, text: String },
764 #[serde(rename_all = "camelCase")]
765 #[ts(rename_all = "camelCase")]
766 Reasoning {
767 id: String,
768 #[serde(default)]
769 summary: Vec<String>,
770 #[serde(default)]
771 content: Vec<String>,
772 },
773 #[serde(rename_all = "camelCase")]
774 #[ts(rename_all = "camelCase")]
775 CommandExecution {
776 id: String,
777 command: String,
779 cwd: PathBuf,
781 status: CommandExecutionStatus,
782 command_actions: Vec<CommandAction>,
786 aggregated_output: Option<String>,
788 exit_code: Option<i32>,
790 duration_ms: Option<i64>,
792 },
793 #[serde(rename_all = "camelCase")]
794 #[ts(rename_all = "camelCase")]
795 FileChange {
796 id: String,
797 changes: Vec<FileUpdateChange>,
798 status: PatchApplyStatus,
799 },
800 #[serde(rename_all = "camelCase")]
801 #[ts(rename_all = "camelCase")]
802 McpToolCall {
803 id: String,
804 server: String,
805 tool: String,
806 status: McpToolCallStatus,
807 arguments: JsonValue,
808 result: Option<McpToolCallResult>,
809 error: Option<McpToolCallError>,
810 },
811 #[serde(rename_all = "camelCase")]
812 #[ts(rename_all = "camelCase")]
813 WebSearch { id: String, query: String },
814 #[serde(rename_all = "camelCase")]
815 #[ts(rename_all = "camelCase")]
816 TodoList { id: String, items: Vec<TodoItem> },
817 #[serde(rename_all = "camelCase")]
818 #[ts(rename_all = "camelCase")]
819 ImageView { id: String, path: String },
820 #[serde(rename_all = "camelCase")]
821 #[ts(rename_all = "camelCase")]
822 CodeReview { id: String, review: String },
823}
824
825impl From<CoreTurnItem> for ThreadItem {
826 fn from(value: CoreTurnItem) -> Self {
827 match value {
828 CoreTurnItem::UserMessage(user) => ThreadItem::UserMessage {
829 id: user.id,
830 content: user.content.into_iter().map(UserInput::from).collect(),
831 },
832 CoreTurnItem::AgentMessage(agent) => {
833 let text = agent
834 .content
835 .into_iter()
836 .map(|entry| match entry {
837 CoreAgentMessageContent::Text { text } => text,
838 })
839 .collect::<String>();
840 ThreadItem::AgentMessage { id: agent.id, text }
841 }
842 CoreTurnItem::Reasoning(reasoning) => ThreadItem::Reasoning {
843 id: reasoning.id,
844 summary: reasoning.summary_text,
845 content: reasoning.raw_content,
846 },
847 CoreTurnItem::WebSearch(search) => ThreadItem::WebSearch {
848 id: search.id,
849 query: search.query,
850 },
851 }
852 }
853}
854
855#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
856#[serde(rename_all = "camelCase")]
857#[ts(export_to = "v2/")]
858pub enum CommandExecutionStatus {
859 InProgress,
860 Completed,
861 Failed,
862 Declined,
863}
864
865#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
866#[serde(rename_all = "camelCase")]
867#[ts(export_to = "v2/")]
868pub struct FileUpdateChange {
869 pub path: String,
870 pub kind: PatchChangeKind,
871 pub diff: String,
872}
873
874#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
875#[serde(tag = "type", rename_all = "camelCase")]
876#[ts(tag = "type")]
877#[ts(export_to = "v2/")]
878pub enum PatchChangeKind {
879 Add,
880 Delete,
881 Update { move_path: Option<PathBuf> },
882}
883
884#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
885#[serde(rename_all = "camelCase")]
886#[ts(export_to = "v2/")]
887pub enum PatchApplyStatus {
888 InProgress,
889 Completed,
890 Failed,
891 Declined,
892}
893
894#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
895#[serde(rename_all = "camelCase")]
896#[ts(export_to = "v2/")]
897pub enum McpToolCallStatus {
898 InProgress,
899 Completed,
900 Failed,
901}
902
903#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
904#[serde(rename_all = "camelCase")]
905#[ts(export_to = "v2/")]
906pub struct McpToolCallResult {
907 pub content: Vec<McpContentBlock>,
908 pub structured_content: Option<JsonValue>,
909}
910
911#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
912#[serde(rename_all = "camelCase")]
913#[ts(export_to = "v2/")]
914pub struct McpToolCallError {
915 pub message: String,
916}
917
918#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
919#[serde(rename_all = "camelCase")]
920#[ts(export_to = "v2/")]
921pub struct TodoItem {
922 pub id: String,
923 pub text: String,
924 pub completed: bool,
925}
926
927#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
930#[serde(rename_all = "camelCase")]
931#[ts(export_to = "v2/")]
932pub struct ThreadStartedNotification {
933 pub thread: Thread,
934}
935
936#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
937#[serde(rename_all = "camelCase")]
938#[ts(export_to = "v2/")]
939pub struct TurnStartedNotification {
940 pub turn: Turn,
941}
942
943#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
944#[serde(rename_all = "camelCase")]
945#[ts(export_to = "v2/")]
946pub struct Usage {
947 pub input_tokens: i32,
948 pub cached_input_tokens: i32,
949 pub output_tokens: i32,
950}
951
952#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
953#[serde(rename_all = "camelCase")]
954#[ts(export_to = "v2/")]
955pub struct TurnCompletedNotification {
956 pub turn: Turn,
957}
958
959#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
960#[serde(rename_all = "camelCase")]
961#[ts(export_to = "v2/")]
962pub struct ItemStartedNotification {
963 pub item: ThreadItem,
964}
965
966#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
967#[serde(rename_all = "camelCase")]
968#[ts(export_to = "v2/")]
969pub struct ItemCompletedNotification {
970 pub item: ThreadItem,
971}
972
973#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
975#[serde(rename_all = "camelCase")]
976#[ts(export_to = "v2/")]
977pub struct AgentMessageDeltaNotification {
978 pub item_id: String,
979 pub delta: String,
980}
981
982#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
983#[serde(rename_all = "camelCase")]
984#[ts(export_to = "v2/")]
985pub struct ReasoningSummaryTextDeltaNotification {
986 pub item_id: String,
987 pub delta: String,
988 pub summary_index: i64,
989}
990
991#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
992#[serde(rename_all = "camelCase")]
993#[ts(export_to = "v2/")]
994pub struct ReasoningSummaryPartAddedNotification {
995 pub item_id: String,
996 pub summary_index: i64,
997}
998
999#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1000#[serde(rename_all = "camelCase")]
1001#[ts(export_to = "v2/")]
1002pub struct ReasoningTextDeltaNotification {
1003 pub item_id: String,
1004 pub delta: String,
1005 pub content_index: i64,
1006}
1007
1008#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1009#[serde(rename_all = "camelCase")]
1010#[ts(export_to = "v2/")]
1011pub struct CommandExecutionOutputDeltaNotification {
1012 pub item_id: String,
1013 pub delta: String,
1014}
1015
1016#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1017#[serde(rename_all = "camelCase")]
1018#[ts(export_to = "v2/")]
1019pub struct McpToolCallProgressNotification {
1020 pub item_id: String,
1021 pub message: String,
1022}
1023
1024#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1025#[serde(rename_all = "camelCase")]
1026#[ts(export_to = "v2/")]
1027pub struct WindowsWorldWritableWarningNotification {
1028 pub sample_paths: Vec<String>,
1029 pub extra_count: usize,
1030 pub failed_scan: bool,
1031}
1032
1033#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1034#[serde(rename_all = "camelCase")]
1035#[ts(export_to = "v2/")]
1036pub struct CommandExecutionRequestApprovalParams {
1037 pub thread_id: String,
1038 pub turn_id: String,
1039 pub item_id: String,
1040 pub reason: Option<String>,
1042 pub risk: Option<SandboxCommandAssessment>,
1044}
1045
1046#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1047#[serde(rename_all = "camelCase")]
1048#[ts(export_to = "v2/")]
1049pub struct CommandExecutionRequestAcceptSettings {
1050 #[serde(default)]
1052 pub for_session: bool,
1053}
1054
1055#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1056#[serde(rename_all = "camelCase")]
1057#[ts(export_to = "v2/")]
1058pub struct CommandExecutionRequestApprovalResponse {
1059 pub decision: ApprovalDecision,
1060 #[serde(default)]
1063 pub accept_settings: Option<CommandExecutionRequestAcceptSettings>,
1064}
1065
1066#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1067#[serde(rename_all = "camelCase")]
1068#[ts(export_to = "v2/")]
1069pub struct FileChangeRequestApprovalParams {
1070 pub thread_id: String,
1071 pub turn_id: String,
1072 pub item_id: String,
1073 pub reason: Option<String>,
1075 pub grant_root: Option<PathBuf>,
1078}
1079
1080#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1081#[ts(export_to = "v2/")]
1082pub struct FileChangeRequestApprovalResponse {
1083 pub decision: ApprovalDecision,
1084}
1085
1086#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1087#[serde(rename_all = "camelCase")]
1088#[ts(export_to = "v2/")]
1089pub struct AccountRateLimitsUpdatedNotification {
1090 pub rate_limits: RateLimitSnapshot,
1091}
1092
1093#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1094#[serde(rename_all = "camelCase")]
1095#[ts(export_to = "v2/")]
1096pub struct RateLimitSnapshot {
1097 pub primary: Option<RateLimitWindow>,
1098 pub secondary: Option<RateLimitWindow>,
1099 pub credits: Option<CreditsSnapshot>,
1100}
1101
1102impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
1103 fn from(value: CoreRateLimitSnapshot) -> Self {
1104 Self {
1105 primary: value.primary.map(RateLimitWindow::from),
1106 secondary: value.secondary.map(RateLimitWindow::from),
1107 credits: value.credits.map(CreditsSnapshot::from),
1108 }
1109 }
1110}
1111
1112#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1113#[serde(rename_all = "camelCase")]
1114#[ts(export_to = "v2/")]
1115pub struct RateLimitWindow {
1116 pub used_percent: i32,
1117 pub window_duration_mins: Option<i64>,
1118 pub resets_at: Option<i64>,
1119}
1120
1121impl From<CoreRateLimitWindow> for RateLimitWindow {
1122 fn from(value: CoreRateLimitWindow) -> Self {
1123 Self {
1124 used_percent: value.used_percent.round() as i32,
1125 window_duration_mins: value.window_minutes,
1126 resets_at: value.resets_at,
1127 }
1128 }
1129}
1130
1131#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1132#[serde(rename_all = "camelCase")]
1133#[ts(export_to = "v2/")]
1134pub struct CreditsSnapshot {
1135 pub has_credits: bool,
1136 pub unlimited: bool,
1137 pub balance: Option<String>,
1138}
1139
1140impl From<CoreCreditsSnapshot> for CreditsSnapshot {
1141 fn from(value: CoreCreditsSnapshot) -> Self {
1142 Self {
1143 has_credits: value.has_credits,
1144 unlimited: value.unlimited,
1145 balance: value.balance,
1146 }
1147 }
1148}
1149
1150#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1151#[serde(rename_all = "camelCase")]
1152#[ts(export_to = "v2/")]
1153pub struct AccountLoginCompletedNotification {
1154 pub login_id: Option<String>,
1157 pub success: bool,
1158 pub error: Option<String>,
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163 use super::*;
1164 use codex_protocol::items::AgentMessageContent;
1165 use codex_protocol::items::AgentMessageItem;
1166 use codex_protocol::items::ReasoningItem;
1167 use codex_protocol::items::TurnItem;
1168 use codex_protocol::items::UserMessageItem;
1169 use codex_protocol::items::WebSearchItem;
1170 use codex_protocol::user_input::UserInput as CoreUserInput;
1171 use pretty_assertions::assert_eq;
1172 use serde_json::json;
1173 use std::path::PathBuf;
1174
1175 #[test]
1176 fn core_turn_item_into_thread_item_converts_supported_variants() {
1177 let user_item = TurnItem::UserMessage(UserMessageItem {
1178 id: "user-1".to_string(),
1179 content: vec![
1180 CoreUserInput::Text {
1181 text: "hello".to_string(),
1182 },
1183 CoreUserInput::Image {
1184 image_url: "https://example.com/image.png".to_string(),
1185 },
1186 CoreUserInput::LocalImage {
1187 path: PathBuf::from("local/image.png"),
1188 },
1189 ],
1190 });
1191
1192 assert_eq!(
1193 ThreadItem::from(user_item),
1194 ThreadItem::UserMessage {
1195 id: "user-1".to_string(),
1196 content: vec![
1197 UserInput::Text {
1198 text: "hello".to_string(),
1199 },
1200 UserInput::Image {
1201 url: "https://example.com/image.png".to_string(),
1202 },
1203 UserInput::LocalImage {
1204 path: PathBuf::from("local/image.png"),
1205 },
1206 ],
1207 }
1208 );
1209
1210 let agent_item = TurnItem::AgentMessage(AgentMessageItem {
1211 id: "agent-1".to_string(),
1212 content: vec![
1213 AgentMessageContent::Text {
1214 text: "Hello ".to_string(),
1215 },
1216 AgentMessageContent::Text {
1217 text: "world".to_string(),
1218 },
1219 ],
1220 });
1221
1222 assert_eq!(
1223 ThreadItem::from(agent_item),
1224 ThreadItem::AgentMessage {
1225 id: "agent-1".to_string(),
1226 text: "Hello world".to_string(),
1227 }
1228 );
1229
1230 let reasoning_item = TurnItem::Reasoning(ReasoningItem {
1231 id: "reasoning-1".to_string(),
1232 summary_text: vec!["line one".to_string(), "line two".to_string()],
1233 raw_content: vec![],
1234 });
1235
1236 assert_eq!(
1237 ThreadItem::from(reasoning_item),
1238 ThreadItem::Reasoning {
1239 id: "reasoning-1".to_string(),
1240 summary: vec!["line one".to_string(), "line two".to_string()],
1241 content: vec![],
1242 }
1243 );
1244
1245 let search_item = TurnItem::WebSearch(WebSearchItem {
1246 id: "search-1".to_string(),
1247 query: "docs".to_string(),
1248 });
1249
1250 assert_eq!(
1251 ThreadItem::from(search_item),
1252 ThreadItem::WebSearch {
1253 id: "search-1".to_string(),
1254 query: "docs".to_string(),
1255 }
1256 );
1257 }
1258
1259 #[test]
1260 fn codex_error_info_serializes_http_status_code_in_camel_case() {
1261 let value = CodexErrorInfo::ResponseTooManyFailedAttempts {
1262 http_status_code: Some(401),
1263 };
1264
1265 assert_eq!(
1266 serde_json::to_value(value).unwrap(),
1267 json!({
1268 "responseTooManyFailedAttempts": {
1269 "httpStatusCode": 401
1270 }
1271 })
1272 );
1273 }
1274}