From 887bec0dee624bbe5f3e3f064a72fb86e563a5af Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 30 Jan 2026 18:23:00 +0100 Subject: [PATCH 001/139] chore: do not clean the DB anymore (#10232) --- codex-rs/core/src/rollout/recorder.rs | 2 +- codex-rs/core/src/state_db.rs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 8157133165..501b46271f 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -428,7 +428,7 @@ impl RolloutRecorder { } }, Err(e) => { - warn!("failed to parse rollout line: {v:?}, error: {e}"); + warn!("failed to parse rollout line: {e}"); parse_errors = parse_errors.saturating_add(1); } } diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index 0804273bba..3e5beaa87e 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -33,12 +33,6 @@ pub(crate) async fn init_if_enabled( ) -> Option { let state_path = config.codex_home.join(STATE_DB_FILENAME); if !config.features.enabled(Feature::Sqlite) { - // We delete the file on best effort basis to maintain retro-compatibility in the future. - let wal_path = state_path.with_extension("sqlite-wal"); - let shm_path = state_path.with_extension("sqlite-shm"); - for path in [state_path.as_path(), wal_path.as_path(), shm_path.as_path()] { - tokio::fs::remove_file(path).await.ok(); - } return None; } let existed = tokio::fs::try_exists(&state_path).await.unwrap_or(false); From eff11f792b6bfae2dffdfa33c904b5732194e560 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 30 Jan 2026 18:23:18 +0100 Subject: [PATCH 002/139] feat: improve logs client (#10229) --- codex-rs/state/src/bin/logs_client.rs | 144 ++++++++++++++++++-------- codex-rs/state/src/model/log.rs | 7 +- codex-rs/state/src/runtime.rs | 58 ++++++++--- 3 files changed, 148 insertions(+), 61 deletions(-) diff --git a/codex-rs/state/src/bin/logs_client.rs b/codex-rs/state/src/bin/logs_client.rs index 4389c29bc6..d1329a1a89 100644 --- a/codex-rs/state/src/bin/logs_client.rs +++ b/codex-rs/state/src/bin/logs_client.rs @@ -3,8 +3,6 @@ use std::time::Duration; use anyhow::Context; use chrono::DateTime; -use chrono::SecondsFormat; -use chrono::Utc; use clap::Parser; use codex_state::LogQuery; use codex_state::LogRow; @@ -37,17 +35,21 @@ struct Args { #[arg(long, value_name = "RFC3339|UNIX")] to: Option, - /// Substring match on module_path. - #[arg(long)] - module: Option, + /// Substring match on module_path. Repeat to include multiple substrings. + #[arg(long = "module")] + module: Vec, - /// Substring match on file path. - #[arg(long)] - file: Option, + /// Substring match on file path. Repeat to include multiple substrings. + #[arg(long = "file")] + file: Vec, - /// Match a specific thread id. + /// Match one or more thread ids. Repeat to include multiple threads. + #[arg(long = "thread-id")] + thread_id: Vec, + + /// Include logs that do not have a thread id. #[arg(long)] - thread_id: Option, + threadless: bool, /// Number of matching rows to show before tailing. #[arg(long, default_value_t = 200)] @@ -63,9 +65,10 @@ struct LogFilter { level_upper: Option, from_ts: Option, to_ts: Option, - module_like: Option, - file_like: Option, - thread_id: Option, + module_like: Vec, + file_like: Vec, + thread_ids: Vec, + include_threadless: bool, } #[tokio::main] @@ -126,14 +129,33 @@ fn build_filter(args: &Args) -> anyhow::Result { .context("failed to parse --to")?; let level_upper = args.level.as_ref().map(|level| level.to_ascii_uppercase()); + let module_like = args + .module + .iter() + .filter(|module| !module.is_empty()) + .cloned() + .collect::>(); + let file_like = args + .file + .iter() + .filter(|file| !file.is_empty()) + .cloned() + .collect::>(); + let thread_ids = args + .thread_id + .iter() + .filter(|thread_id| !thread_id.is_empty()) + .cloned() + .collect::>(); Ok(LogFilter { level_upper, from_ts, to_ts, - module_like: args.module.clone(), - file_like: args.file.clone(), - thread_id: args.thread_id.clone(), + module_like, + file_like, + thread_ids, + include_threadless: args.threadless, }) } @@ -211,7 +233,8 @@ fn to_log_query( to_ts: filter.to_ts, module_like: filter.module_like.clone(), file_like: filter.file_like.clone(), - thread_id: filter.thread_id.clone(), + thread_ids: filter.thread_ids.clone(), + include_threadless: filter.include_threadless, after_id, limit, descending, @@ -219,45 +242,82 @@ fn to_log_query( } fn format_row(row: &LogRow) -> String { - let timestamp = format_timestamp(row.ts, row.ts_nanos); + let timestamp = formatter::ts(row.ts, row.ts_nanos); let level = row.level.as_str(); let target = row.target.as_str(); let message = row.message.as_deref().unwrap_or(""); - let level_colored = color_level(level); + let level_colored = formatter::level(level); let timestamp_colored = timestamp.dimmed().to_string(); let thread_id = row.thread_id.as_deref().unwrap_or("-"); let thread_id_colored = thread_id.blue().dimmed().to_string(); let target_colored = target.dimmed().to_string(); - let message_colored = message.bold().to_string(); + let message_colored = heuristic_formatting(message); format!( "{timestamp_colored} {level_colored} [{thread_id_colored}] {target_colored} - {message_colored}" ) } -fn color_level(level: &str) -> String { - let padded = format!("{level:<5}"); - if level.eq_ignore_ascii_case("error") { - return padded.red().bold().to_string(); +fn heuristic_formatting(message: &str) -> String { + if matcher::apply_patch(message) { + formatter::apply_patch(message) + } else { + message.bold().to_string() } - if level.eq_ignore_ascii_case("warn") { - return padded.yellow().bold().to_string(); - } - if level.eq_ignore_ascii_case("info") { - return padded.green().bold().to_string(); - } - if level.eq_ignore_ascii_case("debug") { - return padded.blue().bold().to_string(); - } - if level.eq_ignore_ascii_case("trace") { - return padded.magenta().bold().to_string(); - } - padded.bold().to_string() } -fn format_timestamp(ts: i64, ts_nanos: i64) -> String { - let nanos = u32::try_from(ts_nanos).unwrap_or(0); - match DateTime::::from_timestamp(ts, nanos) { - Some(dt) => dt.to_rfc3339_opts(SecondsFormat::Millis, true), - None => format!("{ts}.{ts_nanos:09}Z"), +mod matcher { + pub(super) fn apply_patch(message: &str) -> bool { + message.starts_with("ToolCall: apply_patch") + } +} + +mod formatter { + use chrono::DateTime; + use chrono::SecondsFormat; + use chrono::Utc; + use owo_colors::OwoColorize; + + pub(super) fn apply_patch(message: &str) -> String { + message + .lines() + .map(|line| { + if line.starts_with('+') { + line.green().bold().to_string() + } else if line.starts_with('-') { + line.red().bold().to_string() + } else { + line.bold().to_string() + } + }) + .collect::>() + .join("\n") + } + + pub(super) fn ts(ts: i64, ts_nanos: i64) -> String { + let nanos = u32::try_from(ts_nanos).unwrap_or(0); + match DateTime::::from_timestamp(ts, nanos) { + Some(dt) => dt.to_rfc3339_opts(SecondsFormat::Millis, true), + None => format!("{ts}.{ts_nanos:09}Z"), + } + } + + pub(super) fn level(level: &str) -> String { + let padded = format!("{level:<5}"); + if level.eq_ignore_ascii_case("error") { + return padded.red().bold().to_string(); + } + if level.eq_ignore_ascii_case("warn") { + return padded.yellow().bold().to_string(); + } + if level.eq_ignore_ascii_case("info") { + return padded.green().bold().to_string(); + } + if level.eq_ignore_ascii_case("debug") { + return padded.blue().bold().to_string(); + } + if level.eq_ignore_ascii_case("trace") { + return padded.magenta().bold().to_string(); + } + padded.bold().to_string() } } diff --git a/codex-rs/state/src/model/log.rs b/codex-rs/state/src/model/log.rs index 5d774249dd..819abb5d22 100644 --- a/codex-rs/state/src/model/log.rs +++ b/codex-rs/state/src/model/log.rs @@ -32,9 +32,10 @@ pub struct LogQuery { pub level_upper: Option, pub from_ts: Option, pub to_ts: Option, - pub module_like: Option, - pub file_like: Option, - pub thread_id: Option, + pub module_like: Vec, + pub file_like: Vec, + pub thread_ids: Vec, + pub include_threadless: bool, pub after_id: Option, pub limit: Option, pub descending: bool, diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index e0805e5b65..39c79c32b9 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -457,28 +457,54 @@ fn push_log_filters<'a>(builder: &mut QueryBuilder<'a, Sqlite>, query: &'a LogQu if let Some(to_ts) = query.to_ts { builder.push(" AND ts <= ").push_bind(to_ts); } - if let Some(module_like) = query.module_like.as_ref() { - builder - .push(" AND module_path LIKE '%' || ") - .push_bind(module_like.as_str()) - .push(" || '%'"); - } - if let Some(file_like) = query.file_like.as_ref() { - builder - .push(" AND file LIKE '%' || ") - .push_bind(file_like.as_str()) - .push(" || '%'"); - } - if let Some(thread_id) = query.thread_id.as_ref() { - builder - .push(" AND thread_id = ") - .push_bind(thread_id.as_str()); + push_like_filters(builder, "module_path", &query.module_like); + push_like_filters(builder, "file", &query.file_like); + let has_thread_filter = !query.thread_ids.is_empty() || query.include_threadless; + if has_thread_filter { + builder.push(" AND ("); + let mut needs_or = false; + for thread_id in &query.thread_ids { + if needs_or { + builder.push(" OR "); + } + builder.push("thread_id = ").push_bind(thread_id.as_str()); + needs_or = true; + } + if query.include_threadless { + if needs_or { + builder.push(" OR "); + } + builder.push("thread_id IS NULL"); + } + builder.push(")"); } if let Some(after_id) = query.after_id { builder.push(" AND id > ").push_bind(after_id); } } +fn push_like_filters<'a>( + builder: &mut QueryBuilder<'a, Sqlite>, + column: &str, + filters: &'a [String], +) { + if filters.is_empty() { + return; + } + builder.push(" AND ("); + for (idx, filter) in filters.iter().enumerate() { + if idx > 0 { + builder.push(" OR "); + } + builder + .push(column) + .push(" LIKE '%' || ") + .push_bind(filter.as_str()) + .push(" || '%'"); + } + builder.push(")"); +} + async fn open_sqlite(path: &Path) -> anyhow::Result { let options = SqliteConnectOptions::new() .filename(path) From 079f4952e0bdb147769849f2f7dcc06d4100bdf0 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 30 Jan 2026 18:26:49 +0100 Subject: [PATCH 003/139] feat: heuristic coloring of logs (#10228) From 0212f4010e2b66eea296202da90778ce9d44babb Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 30 Jan 2026 18:32:10 +0100 Subject: [PATCH 004/139] nit: fix db with multiple metadata lines (#10237) --- codex-rs/core/src/rollout/recorder.rs | 2 +- codex-rs/state/src/extract.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 501b46271f..265ff2bde2 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -434,7 +434,7 @@ impl RolloutRecorder { } } - info!( + tracing::debug!( "Resumed rollout with {} items, thread ID: {:?}, parse errors: {}", items.len(), thread_id, diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 9c8ce21556..8d327a4d1a 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -30,6 +30,11 @@ pub fn apply_rollout_item( } fn apply_session_meta_from_item(metadata: &mut ThreadMetadata, meta_line: &SessionMetaLine) { + if metadata.id != meta_line.meta.id { + // Ignore session_meta lines that don't match the canonical thread ID, + // e.g., forked rollouts that embed the source session metadata. + return; + } metadata.id = meta_line.meta.id; metadata.source = enum_to_string(&meta_line.meta.source); if let Some(provider) = meta_line.meta.model_provider.as_deref() { From 377ab0c77cd8609637f2fa6d7c0230fa69ce3acc Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 30 Jan 2026 09:33:23 -0800 Subject: [PATCH 005/139] feat: refactor CodexAuth so invalid state cannot be represented (#10208) Previously, `CodexAuth` was defined as follows: https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L39-L46 But if you looked at its constructors, we had creation for `AuthMode::ApiKey` where `storage` was built using a nonsensical path (`PathBuf::new()`) and `auth_dot_json` was `None`: https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L212-L220 By comparison, when `AuthMode::ChatGPT` was used, `api_key` was always `None`: https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L665-L671 https://github.com/openai/codex/pull/10012 took things further because it introduced a new `ChatgptAuthTokens` variant to `AuthMode`, which is important in when invoking `account/login/start` via the app server, but most logic _internal_ to the app server should just reason about two `AuthMode` variants: `ApiKey` and `ChatGPT`. This PR tries to clean things up as follows: - `LoginAccountParams` and `AuthMode` in `codex-rs/app-server-protocol/` both continue to have the `ChatgptAuthTokens` variant, though it is used exclusively for the on-the-wire messaging. - `codex-rs/core/src/auth.rs` now has its own `AuthMode` enum, which only has two variants: `ApiKey` and `ChatGPT`. - `CodexAuth` has been changed from a struct to an enum. It is a disjoint union where each variant (`ApiKey`, `ChatGpt`, and `ChatGptAuthTokens`) have only the associated fields that make sense for that variant. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10208). * #10224 * __->__ #10208 --- .../src/protocol/common.rs | 4 - .../app-server/src/codex_message_processor.rs | 39 +- codex-rs/cli/src/login.rs | 2 +- codex-rs/core/src/auth.rs | 340 ++++++++++++------ codex-rs/core/src/client.rs | 14 +- codex-rs/core/src/codex.rs | 2 +- codex-rs/core/src/model_provider_info.rs | 9 +- codex-rs/core/src/models_manager/manager.rs | 14 +- .../core/src/models_manager/model_presets.rs | 2 +- codex-rs/tui/src/app.rs | 2 +- codex-rs/tui/src/chatwidget.rs | 13 +- codex-rs/tui/src/lib.rs | 2 +- codex-rs/tui/src/status/helpers.rs | 8 +- 13 files changed, 286 insertions(+), 165 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 2e8778bdc5..67736374ec 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -24,10 +24,6 @@ impl GitSha { } /// Authentication mode for OpenAI-backed providers. -/// -/// This is used internally to determine the base URL for generating responses, and to gate -/// ChatGPT-only behaviors like rate limits and available models (as opposed to API key-based -/// auth). #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)] #[serde(rename_all = "lowercase")] pub enum AuthMode { diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 3817bce3a1..c02f3c7368 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -133,6 +133,7 @@ use codex_app_server_protocol::build_turns_from_event_msgs; use codex_backend_client::Client as BackendClient; use codex_chatgpt::connectors; use codex_core::AuthManager; +use codex_core::CodexAuth; use codex_core::CodexThread; use codex_core::Cursor as RolloutCursor; use codex_core::InitialHistory; @@ -686,7 +687,11 @@ impl CodexMessageProcessor { .await; let payload = AuthStatusChangeNotification { - auth_method: self.auth_manager.auth_cached().map(|auth| auth.mode), + auth_method: self + .auth_manager + .auth_cached() + .as_ref() + .map(CodexAuth::api_auth_mode), }; self.outgoing .send_server_notification(ServerNotification::AuthStatusChange(payload)) @@ -716,7 +721,11 @@ impl CodexMessageProcessor { .await; let payload_v2 = AccountUpdatedNotification { - auth_mode: self.auth_manager.auth_cached().map(|auth| auth.mode), + auth_mode: self + .auth_manager + .auth_cached() + .as_ref() + .map(CodexAuth::api_auth_mode), }; self.outgoing .send_server_notification(ServerNotification::AccountUpdated(payload_v2)) @@ -812,7 +821,10 @@ impl CodexMessageProcessor { auth_manager.reload(); // Notify clients with the actual current auth mode. - let current_auth_method = auth_manager.auth_cached().map(|a| a.mode); + let current_auth_method = auth_manager + .auth_cached() + .as_ref() + .map(CodexAuth::api_auth_mode); let payload = AuthStatusChangeNotification { auth_method: current_auth_method, }; @@ -902,7 +914,10 @@ impl CodexMessageProcessor { auth_manager.reload(); // Notify clients with the actual current auth mode. - let current_auth_method = auth_manager.auth_cached().map(|a| a.mode); + let current_auth_method = auth_manager + .auth_cached() + .as_ref() + .map(CodexAuth::api_auth_mode); let payload_v2 = AccountUpdatedNotification { auth_mode: current_auth_method, }; @@ -1106,7 +1121,11 @@ impl CodexMessageProcessor { } // Reflect the current auth method after logout (likely None). - Ok(self.auth_manager.auth_cached().map(|auth| auth.mode)) + Ok(self + .auth_manager + .auth_cached() + .as_ref() + .map(CodexAuth::api_auth_mode)) } async fn logout_v1(&mut self, request_id: RequestId) { @@ -1178,7 +1197,7 @@ impl CodexMessageProcessor { } else { match self.auth_manager.auth().await { Some(auth) => { - let auth_mode = auth.mode; + let auth_mode = auth.api_auth_mode(); let (reported_auth_method, token_opt) = match auth.get_token() { Ok(token) if !token.is_empty() => { let tok = if include_token { Some(token) } else { None }; @@ -1225,9 +1244,9 @@ impl CodexMessageProcessor { } let account = match self.auth_manager.auth_cached() { - Some(auth) => Some(match auth.mode { - AuthMode::ApiKey => Account::ApiKey {}, - AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens => { + Some(auth) => Some(match auth { + CodexAuth::ApiKey(_) => Account::ApiKey {}, + CodexAuth::ChatGpt(_) | CodexAuth::ChatGptAuthTokens(_) => { let email = auth.get_account_email(); let plan_type = auth.account_plan_type(); @@ -1286,7 +1305,7 @@ impl CodexMessageProcessor { }); }; - if !matches!(auth.mode, AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) { + if !auth.is_chatgpt_auth() { return Err(JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: "chatgpt authentication required to read rate limits".to_string(), diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index 0f4e4eb39f..cee002f339 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -225,7 +225,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { let config = load_config_or_exit(cli_config_overrides).await; match CodexAuth::from_auth_storage(&config.codex_home, config.cli_auth_credentials_store_mode) { - Ok(Some(auth)) => match auth.mode { + Ok(Some(auth)) => match auth.api_auth_mode() { AuthMode::ApiKey => match auth.get_token() { Ok(api_key) => { eprintln!("Logged in using an API key - {}", safe_format_key(&api_key)); diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index d4520dc855..4bddbe3031 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -13,8 +13,9 @@ use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; +use std::sync::RwLock; -use codex_app_server_protocol::AuthMode; +use codex_app_server_protocol::AuthMode as ApiAuthMode; use codex_protocol::config_types::ForcedLoginMethod; pub use crate::auth::storage::AuthCredentialsStoreMode; @@ -35,19 +36,50 @@ use codex_protocol::account::PlanType as AccountPlanType; use serde_json::Value; use thiserror::Error; -#[derive(Debug, Clone)] -pub struct CodexAuth { - pub mode: AuthMode, +/// Account type for the current user. +/// +/// This is used internally to determine the base URL for generating responses, +/// and to gate ChatGPT-only behaviors like rate limits and available models (as +/// opposed to API key-based auth). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AuthMode { + ApiKey, + ChatGPT, +} - pub(crate) api_key: Option, - pub(crate) auth_dot_json: Arc>>, +/// Authentication mechanism used by the current user. +#[derive(Debug, Clone)] +pub enum CodexAuth { + ApiKey(ApiKeyAuth), + ChatGpt(ChatGptAuth), + ChatGptAuthTokens(ChatGptAuthTokens), +} + +#[derive(Debug, Clone)] +pub struct ApiKeyAuth { + api_key: String, +} + +#[derive(Debug, Clone)] +pub struct ChatGptAuth { + state: ChatGptAuthState, storage: Arc, - pub(crate) client: CodexHttpClient, +} + +#[derive(Debug, Clone)] +pub struct ChatGptAuthTokens { + state: ChatGptAuthState, +} + +#[derive(Debug, Clone)] +struct ChatGptAuthState { + auth_dot_json: Arc>>, + client: CodexHttpClient, } impl PartialEq for CodexAuth { fn eq(&self, other: &Self) -> bool { - self.mode == other.mode + self.api_auth_mode() == other.api_auth_mode() } } @@ -114,14 +146,78 @@ impl From for std::io::Error { } impl CodexAuth { + fn from_auth_dot_json( + codex_home: &Path, + auth_dot_json: AuthDotJson, + auth_credentials_store_mode: AuthCredentialsStoreMode, + client: CodexHttpClient, + ) -> std::io::Result { + let auth_mode = auth_dot_json.resolved_mode(); + if auth_mode == ApiAuthMode::ApiKey { + let Some(api_key) = auth_dot_json.openai_api_key.as_deref() else { + return Err(std::io::Error::other("API key auth is missing a key.")); + }; + return Ok(CodexAuth::from_api_key_with_client(api_key, client)); + } + + let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode); + let state = ChatGptAuthState { + auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))), + client, + }; + + match auth_mode { + ApiAuthMode::ChatGPT => { + let storage = create_auth_storage(codex_home.to_path_buf(), storage_mode); + Ok(Self::ChatGpt(ChatGptAuth { state, storage })) + } + ApiAuthMode::ChatgptAuthTokens => { + Ok(Self::ChatGptAuthTokens(ChatGptAuthTokens { state })) + } + ApiAuthMode::ApiKey => unreachable!("api key mode is handled above"), + } + } + /// Loads the available auth information from auth storage. pub fn from_auth_storage( codex_home: &Path, auth_credentials_store_mode: AuthCredentialsStoreMode, - ) -> std::io::Result> { + ) -> std::io::Result> { load_auth(codex_home, false, auth_credentials_store_mode) } + pub fn internal_auth_mode(&self) -> AuthMode { + match self { + Self::ApiKey(_) => AuthMode::ApiKey, + Self::ChatGpt(_) | Self::ChatGptAuthTokens(_) => AuthMode::ChatGPT, + } + } + + pub fn api_auth_mode(&self) -> ApiAuthMode { + match self { + Self::ApiKey(_) => ApiAuthMode::ApiKey, + Self::ChatGpt(_) => ApiAuthMode::ChatGPT, + Self::ChatGptAuthTokens(_) => ApiAuthMode::ChatgptAuthTokens, + } + } + + pub fn is_chatgpt_auth(&self) -> bool { + self.internal_auth_mode() == AuthMode::ChatGPT + } + + pub fn is_external_chatgpt_tokens(&self) -> bool { + matches!(self, Self::ChatGptAuthTokens(_)) + } + + /// Returns `None` is `is_internal_auth_mode() != AuthMode::ApiKey`. + pub fn api_key(&self) -> Option<&str> { + match self { + Self::ApiKey(auth) => Some(auth.api_key.as_str()), + Self::ChatGpt(_) | Self::ChatGptAuthTokens(_) => None, + } + } + + /// Returns `Err` if `is_chatgpt_auth()` is false. pub fn get_token_data(&self) -> Result { let auth_dot_json: Option = self.get_current_auth_json(); match auth_dot_json { @@ -134,20 +230,23 @@ impl CodexAuth { } } + /// Returns the token string used for bearer authentication. pub fn get_token(&self) -> Result { - match self.mode { - AuthMode::ApiKey => Ok(self.api_key.clone().unwrap_or_default()), - AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens => { + match self { + Self::ApiKey(auth) => Ok(auth.api_key.clone()), + Self::ChatGpt(_) | Self::ChatGptAuthTokens(_) => { let access_token = self.get_token_data()?.access_token; Ok(access_token) } } } + /// Returns `None` if `is_chatgpt_auth()` is false. pub fn get_account_id(&self) -> Option { self.get_current_token_data().and_then(|t| t.account_id) } + /// Returns `None` if `is_chatgpt_auth()` is false. pub fn get_account_email(&self) -> Option { self.get_current_token_data().and_then(|t| t.id_token.email) } @@ -176,11 +275,18 @@ impl CodexAuth { }) } + /// Returns `None` if `is_chatgpt_auth()` is false. fn get_current_auth_json(&self) -> Option { + let state = match self { + Self::ChatGpt(auth) => &auth.state, + Self::ChatGptAuthTokens(auth) => &auth.state, + Self::ApiKey(_) => return None, + }; #[expect(clippy::unwrap_used)] - self.auth_dot_json.lock().unwrap().clone() + state.auth_dot_json.lock().unwrap().clone() } + /// Returns `None` if `is_chatgpt_auth()` is false. fn get_current_token_data(&self) -> Option { self.get_current_auth_json().and_then(|t| t.tokens) } @@ -188,7 +294,7 @@ impl CodexAuth { /// Consider this private to integration tests. pub fn create_dummy_chatgpt_auth_for_testing() -> Self { let auth_dot_json = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(ApiAuthMode::ChatGPT), openai_api_key: None, tokens: Some(TokenData { id_token: Default::default(), @@ -199,24 +305,19 @@ impl CodexAuth { last_refresh: Some(Utc::now()), }; - let auth_dot_json = Arc::new(Mutex::new(Some(auth_dot_json))); - Self { - api_key: None, - mode: AuthMode::ChatGPT, - storage: create_auth_storage(PathBuf::new(), AuthCredentialsStoreMode::File), - auth_dot_json, - client: crate::default_client::create_client(), - } + let client = crate::default_client::create_client(); + let state = ChatGptAuthState { + auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))), + client, + }; + let storage = create_auth_storage(PathBuf::new(), AuthCredentialsStoreMode::File); + Self::ChatGpt(ChatGptAuth { state, storage }) } - fn from_api_key_with_client(api_key: &str, client: CodexHttpClient) -> Self { - Self { - api_key: Some(api_key.to_owned()), - mode: AuthMode::ApiKey, - storage: create_auth_storage(PathBuf::new(), AuthCredentialsStoreMode::File), - auth_dot_json: Arc::new(Mutex::new(None)), - client, - } + fn from_api_key_with_client(api_key: &str, _client: CodexHttpClient) -> Self { + Self::ApiKey(ApiKeyAuth { + api_key: api_key.to_owned(), + }) } pub fn from_api_key(api_key: &str) -> Self { @@ -224,6 +325,25 @@ impl CodexAuth { } } +impl ChatGptAuth { + fn current_auth_json(&self) -> Option { + #[expect(clippy::unwrap_used)] + self.state.auth_dot_json.lock().unwrap().clone() + } + + fn current_token_data(&self) -> Option { + self.current_auth_json().and_then(|auth| auth.tokens) + } + + fn storage(&self) -> &Arc { + &self.storage + } + + fn client(&self) -> &CodexHttpClient { + &self.state.client + } +} + pub const OPENAI_API_KEY_ENV_VAR: &str = "OPENAI_API_KEY"; pub const CODEX_API_KEY_ENV_VAR: &str = "CODEX_API_KEY"; @@ -258,7 +378,7 @@ pub fn login_with_api_key( auth_credentials_store_mode: AuthCredentialsStoreMode, ) -> std::io::Result<()> { let auth_dot_json = AuthDotJson { - auth_mode: Some(AuthMode::ApiKey), + auth_mode: Some(ApiAuthMode::ApiKey), openai_api_key: Some(api_key.to_string()), tokens: None, last_refresh: None, @@ -314,10 +434,10 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { }; if let Some(required_method) = config.forced_login_method { - let method_violation = match (required_method, auth.mode) { + let method_violation = match (required_method, auth.internal_auth_mode()) { (ForcedLoginMethod::Api, AuthMode::ApiKey) => None, - (ForcedLoginMethod::Chatgpt, AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) => None, - (ForcedLoginMethod::Api, AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) => Some( + (ForcedLoginMethod::Chatgpt, AuthMode::ChatGPT) => None, + (ForcedLoginMethod::Api, AuthMode::ChatGPT) => Some( "API key login is required, but ChatGPT is currently being used. Logging out." .to_string(), ), @@ -337,7 +457,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { } if let Some(expected_account_id) = config.forced_chatgpt_workspace_id.as_deref() { - if !matches!(auth.mode, AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) { + if !auth.is_chatgpt_auth() { return Ok(()); } @@ -607,7 +727,7 @@ impl AuthDotJson { }; Self { - auth_mode: Some(AuthMode::ChatgptAuthTokens), + auth_mode: Some(ApiAuthMode::ChatgptAuthTokens), openai_api_key: None, tokens: Some(tokens), last_refresh: Some(Utc::now()), @@ -623,21 +743,21 @@ impl AuthDotJson { Ok(Self::from_external_tokens(&external, id_token_info)) } - fn resolved_mode(&self) -> AuthMode { + fn resolved_mode(&self) -> ApiAuthMode { if let Some(mode) = self.auth_mode { return mode; } if self.openai_api_key.is_some() { - return AuthMode::ApiKey; + return ApiAuthMode::ApiKey; } - AuthMode::ChatGPT + ApiAuthMode::ChatGPT } fn storage_mode( &self, auth_credentials_store_mode: AuthCredentialsStoreMode, ) -> AuthCredentialsStoreMode { - if self.resolved_mode() == AuthMode::ChatgptAuthTokens { + if self.resolved_mode() == ApiAuthMode::ChatgptAuthTokens { AuthCredentialsStoreMode::Ephemeral } else { auth_credentials_store_mode @@ -645,35 +765,6 @@ impl AuthDotJson { } } -impl CodexAuth { - fn from_auth_dot_json( - codex_home: &Path, - auth_dot_json: AuthDotJson, - auth_credentials_store_mode: AuthCredentialsStoreMode, - client: CodexHttpClient, - ) -> std::io::Result { - let auth_mode = auth_dot_json.resolved_mode(); - if auth_mode == AuthMode::ApiKey { - let Some(api_key) = auth_dot_json.openai_api_key.as_deref() else { - return Err(std::io::Error::other("API key auth is missing a key.")); - }; - return Ok(CodexAuth::from_api_key_with_client(api_key, client)); - } - - let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode); - let storage = create_auth_storage(codex_home.to_path_buf(), storage_mode); - Ok(Self { - api_key: None, - mode: auth_mode, - storage, - auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))), - client, - }) - } -} - -use std::sync::RwLock; - /// Internal cached auth state. #[derive(Clone)] struct CachedAuth { @@ -685,7 +776,10 @@ struct CachedAuth { impl Debug for CachedAuth { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CachedAuth") - .field("auth_mode", &self.auth.as_ref().map(|auth| auth.mode)) + .field( + "auth_mode", + &self.auth.as_ref().map(CodexAuth::api_auth_mode), + ) .field( "external_refresher", &self.external_refresher.as_ref().map(|_| "present"), @@ -736,11 +830,13 @@ impl UnauthorizedRecovery { fn new(manager: Arc) -> Self { let cached_auth = manager.auth_cached(); let expected_account_id = cached_auth.as_ref().and_then(CodexAuth::get_account_id); - let mode = match cached_auth { - Some(auth) if auth.mode == AuthMode::ChatgptAuthTokens => { - UnauthorizedRecoveryMode::External - } - _ => UnauthorizedRecoveryMode::Managed, + let mode = if cached_auth + .as_ref() + .is_some_and(CodexAuth::is_external_chatgpt_tokens) + { + UnauthorizedRecoveryMode::External + } else { + UnauthorizedRecoveryMode::Managed }; let step = match mode { UnauthorizedRecoveryMode::Managed => UnauthorizedRecoveryStep::Reload, @@ -755,9 +851,12 @@ impl UnauthorizedRecovery { } pub fn has_next(&self) -> bool { - if !self.manager.auth_cached().is_some_and(|auth| { - matches!(auth.mode, AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) - }) { + if !self + .manager + .auth_cached() + .as_ref() + .is_some_and(CodexAuth::is_chatgpt_auth) + { return false; } @@ -996,7 +1095,8 @@ impl AuthManager { pub fn is_external_auth_active(&self) -> bool { self.auth_cached() - .is_some_and(|auth| auth.mode == AuthMode::ChatgptAuthTokens) + .as_ref() + .is_some_and(CodexAuth::is_external_chatgpt_tokens) } /// Convenience constructor returning an `Arc` wrapper. @@ -1026,18 +1126,25 @@ impl AuthManager { Some(auth) => auth, None => return Ok(()), }; - if auth.mode == AuthMode::ChatgptAuthTokens { - return self - .refresh_external_auth(ExternalAuthRefreshReason::Unauthorized) - .await; + match auth { + CodexAuth::ChatGptAuthTokens(_) => { + self.refresh_external_auth(ExternalAuthRefreshReason::Unauthorized) + .await + } + CodexAuth::ChatGpt(chatgpt_auth) => { + let token_data = chatgpt_auth.current_token_data().ok_or_else(|| { + RefreshTokenError::Transient(std::io::Error::other( + "Token data is not available.", + )) + })?; + self.refresh_tokens(&chatgpt_auth, token_data.refresh_token) + .await?; + // Reload to pick up persisted changes. + self.reload(); + Ok(()) + } + CodexAuth::ApiKey(_) => Ok(()), } - let token_data = auth.get_current_token_data().ok_or_else(|| { - RefreshTokenError::Transient(std::io::Error::other("Token data is not available.")) - })?; - self.refresh_tokens(&auth, token_data.refresh_token).await?; - // Reload to pick up persisted changes. - self.reload(); - Ok(()) } /// Log out by deleting the on‑disk auth.json (if present). Returns Ok(true) @@ -1051,16 +1158,23 @@ impl AuthManager { Ok(removed) } - pub fn get_auth_mode(&self) -> Option { - self.auth_cached().map(|a| a.mode) + pub fn get_auth_mode(&self) -> Option { + self.auth_cached().as_ref().map(CodexAuth::api_auth_mode) + } + + pub fn get_internal_auth_mode(&self) -> Option { + self.auth_cached() + .as_ref() + .map(CodexAuth::internal_auth_mode) } async fn refresh_if_stale(&self, auth: &CodexAuth) -> Result { - if auth.mode != AuthMode::ChatGPT { - return Ok(false); - } + let chatgpt_auth = match auth { + CodexAuth::ChatGpt(chatgpt_auth) => chatgpt_auth, + _ => return Ok(false), + }; - let auth_dot_json = match auth.get_current_auth_json() { + let auth_dot_json = match chatgpt_auth.current_auth_json() { Some(auth_dot_json) => auth_dot_json, None => return Ok(false), }; @@ -1075,7 +1189,8 @@ impl AuthManager { if last_refresh >= Utc::now() - chrono::Duration::days(TOKEN_REFRESH_INTERVAL) { return Ok(false); } - self.refresh_tokens(auth, tokens.refresh_token).await?; + self.refresh_tokens(chatgpt_auth, tokens.refresh_token) + .await?; self.reload(); Ok(true) } @@ -1135,13 +1250,13 @@ impl AuthManager { async fn refresh_tokens( &self, - auth: &CodexAuth, + auth: &ChatGptAuth, refresh_token: String, ) -> Result<(), RefreshTokenError> { - let refresh_response = try_refresh_token(refresh_token, &auth.client).await?; + let refresh_response = try_refresh_token(refresh_token, auth.client()).await?; update_tokens( - &auth.storage, + auth.storage(), refresh_response.id_token, refresh_response.access_token, refresh_response.refresh_token, @@ -1254,26 +1369,21 @@ mod tests { ) .expect("failed to write auth file"); - let CodexAuth { - api_key, - mode, - auth_dot_json, - storage: _, - .. - } = super::load_auth(codex_home.path(), false, AuthCredentialsStoreMode::File) + let auth = super::load_auth(codex_home.path(), false, AuthCredentialsStoreMode::File) .unwrap() .unwrap(); - assert_eq!(None, api_key); - assert_eq!(AuthMode::ChatGPT, mode); + assert_eq!(None, auth.api_key()); + assert_eq!(AuthMode::ChatGPT, auth.internal_auth_mode()); - let guard = auth_dot_json.lock().unwrap(); - let auth_dot_json = guard.as_ref().expect("AuthDotJson should exist"); + let auth_dot_json = auth + .get_current_auth_json() + .expect("AuthDotJson should exist"); let last_refresh = auth_dot_json .last_refresh .expect("last_refresh should be recorded"); assert_eq!( - &AuthDotJson { + AuthDotJson { auth_mode: None, openai_api_key: None, tokens: Some(TokenData { @@ -1308,8 +1418,8 @@ mod tests { let auth = super::load_auth(dir.path(), false, AuthCredentialsStoreMode::File) .unwrap() .unwrap(); - assert_eq!(auth.mode, AuthMode::ApiKey); - assert_eq!(auth.api_key, Some("sk-test-key".to_string())); + assert_eq!(auth.internal_auth_mode(), AuthMode::ApiKey); + assert_eq!(auth.api_key(), Some("sk-test-key")); assert!(auth.get_token_data().is_err()); } @@ -1318,7 +1428,7 @@ mod tests { fn logout_removes_auth_file() -> Result<(), std::io::Error> { let dir = tempdir()?; let auth_dot_json = AuthDotJson { - auth_mode: Some(AuthMode::ApiKey), + auth_mode: Some(ApiAuthMode::ApiKey), openai_api_key: Some("sk-test-key".to_string()), tokens: None, last_refresh: None, diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 0f05f58c47..f01c145e56 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -27,7 +27,6 @@ use codex_api::common::ResponsesWsRequest; use codex_api::create_text_param_for_request; use codex_api::error::ApiError; use codex_api::requests::responses::Compression; -use codex_app_server_protocol::AuthMode; use codex_otel::OtelManager; use codex_protocol::ThreadId; @@ -50,6 +49,7 @@ use tokio::sync::mpsc; use tracing::warn; use crate::AuthManager; +use crate::auth::CodexAuth; use crate::auth::RefreshTokenError; use crate::client_common::Prompt; use crate::client_common::ResponseEvent; @@ -220,7 +220,7 @@ impl ModelClient { let api_provider = self .state .provider - .to_api_provider(auth.as_ref().map(|a| a.mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); let request_telemetry = self.build_request_telemetry(); @@ -469,9 +469,7 @@ impl ModelClientSession { .config .features .enabled(Feature::EnableRequestCompression) - && auth.is_some_and(|auth| { - matches!(auth.mode, AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) - }) + && auth.is_some_and(CodexAuth::is_chatgpt_auth) && self.state.provider.is_openai() { Compression::Zstd @@ -509,7 +507,7 @@ impl ModelClientSession { let api_provider = self .state .provider - .to_api_provider(auth.as_ref().map(|a| a.mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); let (request_telemetry, sse_telemetry) = self.build_streaming_telemetry(); @@ -565,7 +563,7 @@ impl ModelClientSession { let api_provider = self .state .provider - .to_api_provider(auth.as_ref().map(|a| a.mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); let (request_telemetry, sse_telemetry) = self.build_streaming_telemetry(); @@ -611,7 +609,7 @@ impl ModelClientSession { let api_provider = self .state .provider - .to_api_provider(auth.as_ref().map(|a| a.mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; let compression = self.responses_request_compression(auth.as_ref()); diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 7eefd04ec4..324f41a04e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -838,7 +838,7 @@ impl Session { session_configuration.collaboration_mode.model(), auth.and_then(CodexAuth::get_account_id), auth.and_then(CodexAuth::get_account_email), - auth.map(|a| a.mode), + auth.map(CodexAuth::api_auth_mode), config.otel.log_user_prompt, terminal::user_agent(), session_configuration.session_source.clone(), diff --git a/codex-rs/core/src/model_provider_info.rs b/codex-rs/core/src/model_provider_info.rs index 0f3551480c..0f5a8643a7 100644 --- a/codex-rs/core/src/model_provider_info.rs +++ b/codex-rs/core/src/model_provider_info.rs @@ -5,10 +5,11 @@ //! 2. User-defined entries inside `~/.codex/config.toml` under the `model_providers` //! key. These override or extend the defaults at runtime. +use crate::auth::AuthMode; +use crate::error::EnvVarError; use codex_api::Provider as ApiProvider; use codex_api::WireApi as ApiWireApi; use codex_api::provider::RetryConfig as ApiRetryConfig; -use codex_app_server_protocol::AuthMode; use http::HeaderMap; use http::header::HeaderName; use http::header::HeaderValue; @@ -19,7 +20,6 @@ use std::collections::HashMap; use std::env::VarError; use std::time::Duration; -use crate::error::EnvVarError; const DEFAULT_STREAM_IDLE_TIMEOUT_MS: u64 = 300_000; const DEFAULT_STREAM_MAX_RETRIES: u64 = 5; const DEFAULT_REQUEST_MAX_RETRIES: u64 = 4; @@ -137,10 +137,7 @@ impl ModelProviderInfo { &self, auth_mode: Option, ) -> crate::error::Result { - let default_base_url = if matches!( - auth_mode, - Some(AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) - ) { + let default_base_url = if matches!(auth_mode, Some(AuthMode::ChatGPT)) { "https://chatgpt.com/backend-api/codex" } else { "https://api.openai.com/v1" diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index c993032ea9..40f9c8ccdc 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -2,6 +2,7 @@ use super::cache::ModelsCacheManager; use crate::api_bridge::auth_provider_from_auth; use crate::api_bridge::map_api_error; use crate::auth::AuthManager; +use crate::auth::AuthMode; use crate::config::Config; use crate::default_client::build_reqwest_client; use crate::error::CodexErr; @@ -13,7 +14,6 @@ use crate::models_manager::model_info; use crate::models_manager::model_presets::builtin_model_presets; use codex_api::ModelsClient; use codex_api::ReqwestTransport; -use codex_app_server_protocol::AuthMode; use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ModelPreset; @@ -61,7 +61,7 @@ impl ModelsManager { let cache_path = codex_home.join(MODEL_CACHE_FILE); let cache_manager = ModelsCacheManager::new(cache_path, DEFAULT_MODEL_CACHE_TTL); Self { - local_models: builtin_model_presets(auth_manager.get_auth_mode()), + local_models: builtin_model_presets(auth_manager.get_internal_auth_mode()), remote_models: RwLock::new(Self::load_remote_models_from_file().unwrap_or_default()), auth_manager, etag: RwLock::new(None), @@ -175,7 +175,7 @@ impl ModelsManager { refresh_strategy: RefreshStrategy, ) -> CoreResult<()> { if !config.features.enabled(Feature::RemoteModels) - || self.auth_manager.get_auth_mode() == Some(AuthMode::ApiKey) + || self.auth_manager.get_internal_auth_mode() == Some(AuthMode::ApiKey) { return Ok(()); } @@ -204,7 +204,7 @@ impl ModelsManager { let _timer = codex_otel::start_global_timer("codex.remote_models.fetch_update.duration_ms", &[]); let auth = self.auth_manager.auth().await; - let auth_mode = self.auth_manager.get_auth_mode(); + let auth_mode = self.auth_manager.get_internal_auth_mode(); let api_provider = self.provider.to_api_provider(auth_mode)?; let api_auth = auth_provider_from_auth(auth.clone(), &self.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); @@ -273,8 +273,8 @@ impl ModelsManager { let existing_presets = self.local_models.clone(); let mut merged_presets = ModelPreset::merge(remote_presets, existing_presets); let chatgpt_mode = matches!( - self.auth_manager.get_auth_mode(), - Some(AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) + self.auth_manager.get_internal_auth_mode(), + Some(AuthMode::ChatGPT) ); merged_presets = ModelPreset::filter_by_auth(merged_presets, chatgpt_mode); @@ -319,7 +319,7 @@ impl ModelsManager { let cache_path = codex_home.join(MODEL_CACHE_FILE); let cache_manager = ModelsCacheManager::new(cache_path, DEFAULT_MODEL_CACHE_TTL); Self { - local_models: builtin_model_presets(auth_manager.get_auth_mode()), + local_models: builtin_model_presets(auth_manager.get_internal_auth_mode()), remote_models: RwLock::new(Self::load_remote_models_from_file().unwrap_or_default()), auth_manager, etag: RwLock::new(None), diff --git a/codex-rs/core/src/models_manager/model_presets.rs b/codex-rs/core/src/models_manager/model_presets.rs index a8f4931f8d..f9105c6448 100644 --- a/codex-rs/core/src/models_manager/model_presets.rs +++ b/codex-rs/core/src/models_manager/model_presets.rs @@ -1,4 +1,4 @@ -use codex_app_server_protocol::AuthMode; +use crate::auth::AuthMode; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ModelUpgrade; use codex_protocol::openai_models::ReasoningEffort; diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 107e9f6ed6..c65ce4498d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -984,7 +984,7 @@ impl App { model.as_str(), auth_ref.and_then(CodexAuth::get_account_id), auth_ref.and_then(CodexAuth::get_account_email), - auth_ref.map(|auth| auth.mode), + auth_ref.map(CodexAuth::api_auth_mode), config.otel.log_user_prompt, codex_core::terminal::user_agent(), SessionSource::Cli, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 8223c40ce0..491ea3add3 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -30,7 +30,6 @@ use std::time::Duration; use std::time::Instant; use crate::version::CODEX_CLI_VERSION; -use codex_app_server_protocol::AuthMode; use codex_backend_client::Client as BackendClient; use codex_chatgpt::connectors; use codex_core::config::Config; @@ -3584,10 +3583,12 @@ impl ChatWidget { fn prefetch_rate_limits(&mut self) { self.stop_rate_limit_poller(); - if !matches!( - self.auth_manager.auth_cached().map(|auth| auth.mode), - Some(AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) - ) { + if !self + .auth_manager + .auth_cached() + .as_ref() + .is_some_and(CodexAuth::is_chatgpt_auth) + { return; } @@ -3600,7 +3601,7 @@ impl ChatWidget { loop { if let Some(auth) = auth_manager.auth().await - && matches!(auth.mode, AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) + && auth.is_chatgpt_auth() && let Some(snapshot) = fetch_rate_limits(base_url.clone(), auth).await { app_event_tx.send(AppEvent::RateLimitSnapshotFetched(snapshot)); diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index a8c6e2b88b..1f34ed6049 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -833,7 +833,7 @@ fn get_login_status(config: &Config) -> LoginStatus { // to refresh the token. Block on it. let codex_home = config.codex_home.clone(); match CodexAuth::from_auth_storage(&codex_home, config.cli_auth_credentials_store_mode) { - Ok(Some(auth)) => LoginStatus::AuthMode(auth.mode), + Ok(Some(auth)) => LoginStatus::AuthMode(auth.api_auth_mode()), Ok(None) => LoginStatus::NotAuthenticated, Err(err) => { error!("Failed to read auth.json: {err}"); diff --git a/codex-rs/tui/src/status/helpers.rs b/codex-rs/tui/src/status/helpers.rs index 91f958a9cf..0a801227f5 100644 --- a/codex-rs/tui/src/status/helpers.rs +++ b/codex-rs/tui/src/status/helpers.rs @@ -2,8 +2,8 @@ use crate::exec_command::relativize_to_home; use crate::text_formatting; use chrono::DateTime; use chrono::Local; -use codex_app_server_protocol::AuthMode; use codex_core::AuthManager; +use codex_core::CodexAuth; use codex_core::config::Config; use codex_core::project_doc::discover_project_doc_paths; use codex_protocol::account::PlanType; @@ -90,15 +90,15 @@ pub(crate) fn compose_account_display( ) -> Option { let auth = auth_manager.auth_cached()?; - match auth.mode { - AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens => { + match auth { + CodexAuth::ChatGpt(_) | CodexAuth::ChatGptAuthTokens(_) => { let email = auth.get_account_email(); let plan = plan .map(|plan_type| title_case(format!("{plan_type:?}").as_str())) .or_else(|| Some("Unknown".to_string())); Some(StatusAccountDisplay::ChatGpt { email, plan }) } - AuthMode::ApiKey => Some(StatusAccountDisplay::ApiKey), + CodexAuth::ApiKey(_) => Some(StatusAccountDisplay::ApiKey), } } From dfafc546ab127aac45a16b73ae3d859203da66d6 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Fri, 30 Jan 2026 10:41:22 -0700 Subject: [PATCH 006/139] chore(feature) Experimental: Personality (#10212) ## Summary Let users start opting in to trying out personalities ## Testing - [x] existing tests pass --- codex-rs/core/src/features.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 961b7a56f6..97fa1022c4 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -560,7 +560,11 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::Personality, key: "personality", - stage: Stage::UnderDevelopment, + stage: Stage::Experimental { + name: "Personality", + menu_description: "Choose a communication style for Codex.", + announcement: "NEW: Pick a personality for Codex. Enable in /experimental!", + }, default_enabled: false, }, FeatureSpec { From 23db79fae2925ea74847d2341bd41f7a0e83344a Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Fri, 30 Jan 2026 10:41:37 -0700 Subject: [PATCH 007/139] chore(feature) Experimental: Smart Approvals (#10211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Let's start getting feedback on this feature 😅 ## Testing - [x] existing tests pass --- codex-rs/core/src/features.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 97fa1022c4..934ab1c0ba 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -464,7 +464,11 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::RequestRule, key: "request_rule", - stage: Stage::UnderDevelopment, + stage: Stage::Experimental { + name: "Smart approvals", + menu_description: "Get smarter \"Don't ask again\" rule requests.", + announcement: "NEW: Try Smart approvals to get smarter \"Don't ask again\" requests. Enable in /experimental!", + }, default_enabled: false, }, FeatureSpec { From 5662eb8b75cd6aa5e1acbdbf93549f85dee510a1 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Fri, 30 Jan 2026 18:04:09 +0000 Subject: [PATCH 008/139] Load exec policy rules from requirements (#10190) `requirements.toml` should be able to specify rules which always run. My intention here was that these rules could only ever be restrictive, which means the decision can be "prompt" or "forbidden" but never "allow". A requirement of "you must always allow this command" didn't make sense to me, but happy to be gaveled otherwise. Rules already applies the most restrictive decision, so we can safely merge these with rules found in other config folders. --- codex-rs/app-server/src/config_api.rs | 1 + codex-rs/cloud-requirements/src/lib.rs | 1 + codex-rs/core/src/config/constraint.rs | 6 + codex-rs/core/src/config/mod.rs | 1 + .../src/config_loader/config_requirements.rs | 98 +++++++ codex-rs/core/src/config_loader/mod.rs | 1 - .../config_loader/requirements_exec_policy.rs | 82 ++++-- codex-rs/core/src/config_loader/tests.rs | 266 ++++++++++++++---- codex-rs/core/src/exec_policy.rs | 13 +- 9 files changed, 398 insertions(+), 71 deletions(-) diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 5c924d1819..9207668486 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -136,6 +136,7 @@ mod tests { CoreSandboxModeRequirement::ExternalSandbox, ]), mcp_servers: None, + rules: None, }; let mapped = map_requirements_toml_to_api(requirements); diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index ccfa600319..37faed081b 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -317,6 +317,7 @@ mod tests { allowed_approval_policies: Some(vec![AskForApproval::Never]), allowed_sandbox_modes: None, mcp_servers: None, + rules: None, }) ); } diff --git a/codex-rs/core/src/config/constraint.rs b/codex-rs/core/src/config/constraint.rs index fa431a6eb2..23b6c57c74 100644 --- a/codex-rs/core/src/config/constraint.rs +++ b/codex-rs/core/src/config/constraint.rs @@ -18,6 +18,12 @@ pub enum ConstraintError { #[error("field `{field_name}` cannot be empty")] EmptyField { field_name: String }, + + #[error("invalid rules in requirements (set by {requirement_source}): {reason}")] + ExecPolicyParse { + requirement_source: RequirementSource, + reason: String, + }, } impl ConstraintError { diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index fbd8999918..2e1c853620 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1512,6 +1512,7 @@ impl Config { approval_policy: mut constrained_approval_policy, sandbox_policy: mut constrained_sandbox_policy, mcp_servers, + exec_policy: _, } = requirements; constrained_approval_policy diff --git a/codex-rs/core/src/config_loader/config_requirements.rs b/codex-rs/core/src/config_loader/config_requirements.rs index 2c220049cf..6b9184a539 100644 --- a/codex-rs/core/src/config_loader/config_requirements.rs +++ b/codex-rs/core/src/config_loader/config_requirements.rs @@ -6,6 +6,8 @@ use serde::Deserialize; use std::collections::BTreeMap; use std::fmt; +use super::requirements_exec_policy::RequirementsExecPolicy; +use super::requirements_exec_policy::RequirementsExecPolicyToml; use crate::config::Constrained; use crate::config::ConstraintError; @@ -49,6 +51,7 @@ pub struct ConfigRequirements { pub approval_policy: Constrained, pub sandbox_policy: Constrained, pub mcp_servers: Option>>, + pub(crate) exec_policy: Option>, } impl Default for ConfigRequirements { @@ -57,6 +60,7 @@ impl Default for ConfigRequirements { approval_policy: Constrained::allow_any_from_default(), sandbox_policy: Constrained::allow_any(SandboxPolicy::ReadOnly), mcp_servers: None, + exec_policy: None, } } } @@ -79,6 +83,7 @@ pub struct ConfigRequirementsToml { pub allowed_approval_policies: Option>, pub allowed_sandbox_modes: Option>, pub mcp_servers: Option>, + pub rules: Option, } /// Value paired with the requirement source it came from, for better error @@ -108,6 +113,7 @@ pub struct ConfigRequirementsWithSources { pub allowed_approval_policies: Option>>, pub allowed_sandbox_modes: Option>>, pub mcp_servers: Option>>, + pub rules: Option>, } impl ConfigRequirementsWithSources { @@ -139,6 +145,7 @@ impl ConfigRequirementsWithSources { allowed_approval_policies, allowed_sandbox_modes, mcp_servers, + rules, } ); } @@ -148,11 +155,13 @@ impl ConfigRequirementsWithSources { allowed_approval_policies, allowed_sandbox_modes, mcp_servers, + rules, } = self; ConfigRequirementsToml { allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value), allowed_sandbox_modes: allowed_sandbox_modes.map(|sourced| sourced.value), mcp_servers: mcp_servers.map(|sourced| sourced.value), + rules: rules.map(|sourced| sourced.value), } } } @@ -189,6 +198,7 @@ impl ConfigRequirementsToml { self.allowed_approval_policies.is_none() && self.allowed_sandbox_modes.is_none() && self.mcp_servers.is_none() + && self.rules.is_none() } } @@ -200,6 +210,7 @@ impl TryFrom for ConfigRequirements { allowed_approval_policies, allowed_sandbox_modes, mcp_servers, + rules, } = toml; let approval_policy: Constrained = match allowed_approval_policies { @@ -274,10 +285,24 @@ impl TryFrom for ConfigRequirements { } None => Constrained::allow_any(default_sandbox_policy), }; + let exec_policy = match rules { + Some(Sourced { value, source }) => { + let policy = value.to_requirements_policy().map_err(|err| { + ConstraintError::ExecPolicyParse { + requirement_source: source.clone(), + reason: err.to_string(), + } + })?; + Some(Sourced::new(policy, source)) + } + None => None, + }; + Ok(ConfigRequirements { approval_policy, sandbox_policy, mcp_servers, + exec_policy, }) } } @@ -286,16 +311,24 @@ impl TryFrom for ConfigRequirements { mod tests { use super::*; use anyhow::Result; + use codex_execpolicy::Decision; + use codex_execpolicy::Evaluation; + use codex_execpolicy::RuleMatch; use codex_protocol::protocol::NetworkAccess; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use toml::from_str; + fn tokens(cmd: &[&str]) -> Vec { + cmd.iter().map(std::string::ToString::to_string).collect() + } + fn with_unknown_source(toml: ConfigRequirementsToml) -> ConfigRequirementsWithSources { let ConfigRequirementsToml { allowed_approval_policies, allowed_sandbox_modes, mcp_servers, + rules, } = toml; ConfigRequirementsWithSources { allowed_approval_policies: allowed_approval_policies @@ -303,6 +336,7 @@ mod tests { allowed_sandbox_modes: allowed_sandbox_modes .map(|value| Sourced::new(value, RequirementSource::Unknown)), mcp_servers: mcp_servers.map(|value| Sourced::new(value, RequirementSource::Unknown)), + rules: rules.map(|value| Sourced::new(value, RequirementSource::Unknown)), } } @@ -323,6 +357,7 @@ mod tests { allowed_approval_policies: Some(allowed_approval_policies.clone()), allowed_sandbox_modes: Some(allowed_sandbox_modes.clone()), mcp_servers: None, + rules: None, }; target.merge_unset_fields(source.clone(), other); @@ -336,6 +371,7 @@ mod tests { )), allowed_sandbox_modes: Some(Sourced::new(allowed_sandbox_modes, source)), mcp_servers: None, + rules: None, } ); } @@ -364,6 +400,7 @@ mod tests { )), allowed_sandbox_modes: None, mcp_servers: None, + rules: None, } ); Ok(()) @@ -400,6 +437,7 @@ mod tests { )), allowed_sandbox_modes: None, mcp_servers: None, + rules: None, } ); Ok(()) @@ -626,4 +664,64 @@ mod tests { ); Ok(()) } + + #[test] + fn deserialize_exec_policy_requirements() -> Result<()> { + let toml_str = r#" + [rules] + prefix_rules = [ + { pattern = [{ token = "rm" }], decision = "forbidden" }, + ] + "#; + let config: ConfigRequirementsToml = from_str(toml_str)?; + let requirements: ConfigRequirements = with_unknown_source(config).try_into()?; + let policy = requirements.exec_policy.expect("exec policy").value; + + assert_eq!( + policy.as_ref().check(&tokens(&["rm", "-rf"]), &|_| { + panic!("rule should match so heuristic should not be called"); + }), + Evaluation { + decision: Decision::Forbidden, + matched_rules: vec![RuleMatch::PrefixRuleMatch { + matched_prefix: tokens(&["rm"]), + decision: Decision::Forbidden, + justification: None, + }], + } + ); + + Ok(()) + } + + #[test] + fn exec_policy_error_includes_requirement_source() -> Result<()> { + let toml_str = r#" + [rules] + prefix_rules = [ + { pattern = [{ token = "rm" }] }, + ] + "#; + let config: ConfigRequirementsToml = from_str(toml_str)?; + let requirements_toml_file = + AbsolutePathBuf::from_absolute_path("/etc/codex/requirements.toml")?; + let source_location = RequirementSource::SystemRequirementsToml { + file: requirements_toml_file, + }; + + let mut requirements_with_sources = ConfigRequirementsWithSources::default(); + requirements_with_sources.merge_unset_fields(source_location.clone(), config); + let err = ConfigRequirements::try_from(requirements_with_sources) + .expect_err("invalid exec policy"); + + assert_eq!( + err, + ConstraintError::ExecPolicyParse { + requirement_source: source_location, + reason: "rules prefix_rule at index 0 is missing a decision".to_string(), + } + ); + + Ok(()) + } } diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 0dd704cab5..eba89fdb3d 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -7,7 +7,6 @@ mod layer_io; mod macos; mod merge; mod overrides; -#[cfg(test)] mod requirements_exec_policy; mod state; diff --git a/codex-rs/core/src/config_loader/requirements_exec_policy.rs b/codex-rs/core/src/config_loader/requirements_exec_policy.rs index cbc1d7531e..74546fc426 100644 --- a/codex-rs/core/src/config_loader/requirements_exec_policy.rs +++ b/codex-rs/core/src/config_loader/requirements_exec_policy.rs @@ -9,16 +9,43 @@ use serde::Deserialize; use std::sync::Arc; use thiserror::Error; -/// TOML types for expressing exec policy requirements. -/// -/// These types are kept separate from `ConfigRequirementsToml` and are -/// converted into `codex-execpolicy` rules. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct RequirementsExecPolicyTomlRoot { - pub exec_policy: RequirementsExecPolicyToml, +#[derive(Debug, Clone)] +pub(crate) struct RequirementsExecPolicy { + policy: Policy, } -/// TOML representation of `[exec_policy]` within `requirements.toml`. +impl RequirementsExecPolicy { + pub fn new(policy: Policy) -> Self { + Self { policy } + } +} + +impl PartialEq for RequirementsExecPolicy { + fn eq(&self, other: &Self) -> bool { + policy_fingerprint(&self.policy) == policy_fingerprint(&other.policy) + } +} + +impl Eq for RequirementsExecPolicy {} + +impl AsRef for RequirementsExecPolicy { + fn as_ref(&self) -> &Policy { + &self.policy + } +} + +fn policy_fingerprint(policy: &Policy) -> Vec { + let mut entries = Vec::new(); + for (program, rules) in policy.rules().iter_all() { + for rule in rules { + entries.push(format!("{program}:{rule:?}")); + } + } + entries.sort(); + entries +} + +/// TOML representation of `[rules]` within `requirements.toml`. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct RequirementsExecPolicyToml { pub prefix_rules: Vec, @@ -65,14 +92,14 @@ impl RequirementsExecPolicyDecisionToml { #[derive(Debug, Error)] pub enum RequirementsExecPolicyParseError { - #[error("exec policy prefix_rules cannot be empty")] + #[error("rules prefix_rules cannot be empty")] EmptyPrefixRules, - #[error("exec policy prefix_rule at index {rule_index} has an empty pattern")] + #[error("rules prefix_rule at index {rule_index} has an empty pattern")] EmptyPattern { rule_index: usize }, #[error( - "exec policy prefix_rule at index {rule_index} has an invalid pattern token at index {token_index}: {reason}" + "rules prefix_rule at index {rule_index} has an invalid pattern token at index {token_index}: {reason}" )] InvalidPatternToken { rule_index: usize, @@ -80,12 +107,20 @@ pub enum RequirementsExecPolicyParseError { reason: String, }, - #[error("exec policy prefix_rule at index {rule_index} has an empty justification")] + #[error("rules prefix_rule at index {rule_index} has an empty justification")] EmptyJustification { rule_index: usize }, + + #[error("rules prefix_rule at index {rule_index} is missing a decision")] + MissingDecision { rule_index: usize }, + + #[error( + "rules prefix_rule at index {rule_index} has decision 'allow', which is not permitted in requirements.toml: Codex merges these rules with other config and uses the most restrictive result (use 'prompt' or 'forbidden')" + )] + AllowDecisionNotAllowed { rule_index: usize }, } impl RequirementsExecPolicyToml { - /// Convert requirements TOML exec policy rules into the internal `.rules` + /// Convert requirements TOML rules into the internal `.rules` /// representation used by `codex-execpolicy`. pub fn to_policy(&self) -> Result { if self.prefix_rules.is_empty() { @@ -112,10 +147,17 @@ impl RequirementsExecPolicyToml { .map(|(token_index, token)| parse_pattern_token(token, rule_index, token_index)) .collect::, _>>()?; - let decision = rule - .decision - .map(RequirementsExecPolicyDecisionToml::as_decision) - .unwrap_or(Decision::Allow); + let decision = match rule.decision { + Some(RequirementsExecPolicyDecisionToml::Allow) => { + return Err(RequirementsExecPolicyParseError::AllowDecisionNotAllowed { + rule_index, + }); + } + Some(decision) => decision.as_decision(), + None => { + return Err(RequirementsExecPolicyParseError::MissingDecision { rule_index }); + } + }; let justification = rule.justification.clone(); let (first_token, remaining_tokens) = pattern_tokens @@ -139,6 +181,12 @@ impl RequirementsExecPolicyToml { Ok(Policy::new(rules_by_program)) } + + pub(crate) fn to_requirements_policy( + &self, + ) -> Result { + self.to_policy().map(RequirementsExecPolicy::new) + } } fn parse_pattern_token( diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index c213c0b2ea..38d104fc19 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -502,6 +502,7 @@ allowed_approval_policies = ["on-request"] allowed_approval_policies: Some(vec![AskForApproval::Never]), allowed_sandbox_modes: None, mcp_servers: None, + rules: None, }, ); load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?; @@ -535,6 +536,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()> allowed_approval_policies: Some(vec![AskForApproval::Never]), allowed_sandbox_modes: None, mcp_servers: None, + rules: None, }; let expected = requirements.clone(); let cloud_requirements = CloudRequirementsLoader::new(async move { Some(requirements) }); @@ -1016,49 +1018,79 @@ async fn project_root_markers_supports_alternate_markers() -> std::io::Result<() } mod requirements_exec_policy_tests { + use super::super::config_requirements::ConfigRequirementsWithSources; use super::super::requirements_exec_policy::RequirementsExecPolicyDecisionToml; + use super::super::requirements_exec_policy::RequirementsExecPolicyParseError; use super::super::requirements_exec_policy::RequirementsExecPolicyPatternTokenToml; use super::super::requirements_exec_policy::RequirementsExecPolicyPrefixRuleToml; use super::super::requirements_exec_policy::RequirementsExecPolicyToml; - use super::super::requirements_exec_policy::RequirementsExecPolicyTomlRoot; + use crate::config_loader::ConfigLayerEntry; + use crate::config_loader::ConfigLayerStack; + use crate::config_loader::ConfigRequirements; + use crate::config_loader::ConfigRequirementsToml; + use crate::config_loader::RequirementSource; + use crate::exec_policy::load_exec_policy; + use codex_app_server_protocol::ConfigLayerSource; use codex_execpolicy::Decision; use codex_execpolicy::Evaluation; use codex_execpolicy::RuleMatch; + use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; + use std::path::Path; + use tempfile::tempdir; + use toml::Value as TomlValue; use toml::from_str; fn tokens(cmd: &[&str]) -> Vec { cmd.iter().map(std::string::ToString::to_string).collect() } - fn allow_all(_: &[String]) -> Decision { - Decision::Allow + fn panic_if_called(_: &[String]) -> Decision { + panic!("rule should match so heuristic should not be called"); + } + + fn config_stack_for_dot_codex_folder_with_requirements( + dot_codex_folder: &Path, + requirements: ConfigRequirements, + ) -> ConfigLayerStack { + let dot_codex_folder = AbsolutePathBuf::from_absolute_path(dot_codex_folder) + .expect("absolute dot_codex_folder"); + let layer = ConfigLayerEntry::new( + ConfigLayerSource::Project { dot_codex_folder }, + TomlValue::Table(Default::default()), + ); + ConfigLayerStack::new(vec![layer], requirements, ConfigRequirementsToml::default()) + .expect("ConfigLayerStack") + } + + fn requirements_from_toml(toml_str: &str) -> ConfigRequirements { + let config: ConfigRequirementsToml = from_str(toml_str).expect("parse requirements toml"); + let mut with_sources = ConfigRequirementsWithSources::default(); + with_sources.merge_unset_fields(RequirementSource::Unknown, config); + ConfigRequirements::try_from(with_sources).expect("requirements") } #[test] fn parses_single_prefix_rule_from_raw_toml() -> anyhow::Result<()> { let toml_str = r#" -[exec_policy] prefix_rules = [ { pattern = [{ token = "rm" }], decision = "forbidden" }, ] "#; - let parsed: RequirementsExecPolicyTomlRoot = from_str(toml_str)?; + let parsed: RequirementsExecPolicyToml = from_str(toml_str)?; assert_eq!( parsed, - RequirementsExecPolicyTomlRoot { - exec_policy: RequirementsExecPolicyToml { - prefix_rules: vec![RequirementsExecPolicyPrefixRuleToml { - pattern: vec![RequirementsExecPolicyPatternTokenToml { - token: Some("rm".to_string()), - any_of: None, - }], - decision: Some(RequirementsExecPolicyDecisionToml::Forbidden), - justification: None, + RequirementsExecPolicyToml { + prefix_rules: vec![RequirementsExecPolicyPrefixRuleToml { + pattern: vec![RequirementsExecPolicyPatternTokenToml { + token: Some("rm".to_string()), + any_of: None, }], - }, + decision: Some(RequirementsExecPolicyDecisionToml::Forbidden), + justification: None, + }], } ); @@ -1068,44 +1100,41 @@ prefix_rules = [ #[test] fn parses_multiple_prefix_rules_from_raw_toml() -> anyhow::Result<()> { let toml_str = r#" -[exec_policy] prefix_rules = [ { pattern = [{ token = "rm" }], decision = "forbidden" }, { pattern = [{ token = "git" }, { any_of = ["push", "commit"] }], decision = "prompt", justification = "review changes before push or commit" }, ] "#; - let parsed: RequirementsExecPolicyTomlRoot = from_str(toml_str)?; + let parsed: RequirementsExecPolicyToml = from_str(toml_str)?; assert_eq!( parsed, - RequirementsExecPolicyTomlRoot { - exec_policy: RequirementsExecPolicyToml { - prefix_rules: vec![ - RequirementsExecPolicyPrefixRuleToml { - pattern: vec![RequirementsExecPolicyPatternTokenToml { - token: Some("rm".to_string()), + RequirementsExecPolicyToml { + prefix_rules: vec![ + RequirementsExecPolicyPrefixRuleToml { + pattern: vec![RequirementsExecPolicyPatternTokenToml { + token: Some("rm".to_string()), + any_of: None, + }], + decision: Some(RequirementsExecPolicyDecisionToml::Forbidden), + justification: None, + }, + RequirementsExecPolicyPrefixRuleToml { + pattern: vec![ + RequirementsExecPolicyPatternTokenToml { + token: Some("git".to_string()), any_of: None, - }], - decision: Some(RequirementsExecPolicyDecisionToml::Forbidden), - justification: None, - }, - RequirementsExecPolicyPrefixRuleToml { - pattern: vec![ - RequirementsExecPolicyPatternTokenToml { - token: Some("git".to_string()), - any_of: None, - }, - RequirementsExecPolicyPatternTokenToml { - token: None, - any_of: Some(vec!["push".to_string(), "commit".to_string()]), - }, - ], - decision: Some(RequirementsExecPolicyDecisionToml::Prompt), - justification: Some("review changes before push or commit".to_string()), - }, - ], - }, + }, + RequirementsExecPolicyPatternTokenToml { + token: None, + any_of: Some(vec!["push".to_string(), "commit".to_string()]), + }, + ], + decision: Some(RequirementsExecPolicyDecisionToml::Prompt), + justification: Some("review changes before push or commit".to_string()), + }, + ], } ); @@ -1115,17 +1144,16 @@ prefix_rules = [ #[test] fn converts_rules_toml_into_internal_policy_representation() -> anyhow::Result<()> { let toml_str = r#" -[exec_policy] prefix_rules = [ { pattern = [{ token = "rm" }], decision = "forbidden" }, ] "#; - let parsed: RequirementsExecPolicyTomlRoot = from_str(toml_str)?; - let policy = parsed.exec_policy.to_policy()?; + let parsed: RequirementsExecPolicyToml = from_str(toml_str)?; + let policy = parsed.to_policy()?; assert_eq!( - policy.check(&tokens(&["rm", "-rf", "/tmp"]), &allow_all), + policy.check(&tokens(&["rm", "-rf", "/tmp"]), &panic_if_called), Evaluation { decision: Decision::Forbidden, matched_rules: vec![RuleMatch::PrefixRuleMatch { @@ -1142,16 +1170,15 @@ prefix_rules = [ #[test] fn head_any_of_expands_into_multiple_program_rules() -> anyhow::Result<()> { let toml_str = r#" -[exec_policy] prefix_rules = [ { pattern = [{ any_of = ["git", "hg"] }, { token = "status" }], decision = "prompt" }, ] "#; - let parsed: RequirementsExecPolicyTomlRoot = from_str(toml_str)?; - let policy = parsed.exec_policy.to_policy()?; + let parsed: RequirementsExecPolicyToml = from_str(toml_str)?; + let policy = parsed.to_policy()?; assert_eq!( - policy.check(&tokens(&["git", "status"]), &allow_all), + policy.check(&tokens(&["git", "status"]), &panic_if_called), Evaluation { decision: Decision::Prompt, matched_rules: vec![RuleMatch::PrefixRuleMatch { @@ -1162,7 +1189,7 @@ prefix_rules = [ } ); assert_eq!( - policy.check(&tokens(&["hg", "status"]), &allow_all), + policy.check(&tokens(&["hg", "status"]), &panic_if_called), Evaluation { decision: Decision::Prompt, matched_rules: vec![RuleMatch::PrefixRuleMatch { @@ -1175,4 +1202,139 @@ prefix_rules = [ Ok(()) } + + #[test] + fn missing_decision_is_rejected() -> anyhow::Result<()> { + let toml_str = r#" +prefix_rules = [ + { pattern = [{ token = "rm" }] }, +] +"#; + + let parsed: RequirementsExecPolicyToml = from_str(toml_str)?; + let err = parsed.to_policy().expect_err("missing decision"); + + assert!(matches!( + err, + RequirementsExecPolicyParseError::MissingDecision { rule_index: 0 } + )); + Ok(()) + } + + #[test] + fn allow_decision_is_rejected() -> anyhow::Result<()> { + let toml_str = r#" +prefix_rules = [ + { pattern = [{ token = "rm" }], decision = "allow" }, +] +"#; + + let parsed: RequirementsExecPolicyToml = from_str(toml_str)?; + let err = parsed.to_policy().expect_err("allow decision not allowed"); + + assert!(matches!( + err, + RequirementsExecPolicyParseError::AllowDecisionNotAllowed { rule_index: 0 } + )); + Ok(()) + } + + #[test] + fn empty_prefix_rules_is_rejected() -> anyhow::Result<()> { + let toml_str = r#" +prefix_rules = [] +"#; + + let parsed: RequirementsExecPolicyToml = from_str(toml_str)?; + let err = parsed.to_policy().expect_err("empty prefix rules"); + + assert!(matches!( + err, + RequirementsExecPolicyParseError::EmptyPrefixRules + )); + Ok(()) + } + + #[tokio::test] + async fn loads_requirements_exec_policy_without_rules_files() -> anyhow::Result<()> { + let temp_dir = tempdir()?; + let requirements = requirements_from_toml( + r#" + [rules] + prefix_rules = [ + { pattern = [{ token = "rm" }], decision = "forbidden" }, + ] + "#, + ); + let config_stack = + config_stack_for_dot_codex_folder_with_requirements(temp_dir.path(), requirements); + + let policy = load_exec_policy(&config_stack).await?; + + assert_eq!( + policy.check_multiple([vec!["rm".to_string()]].iter(), &panic_if_called), + Evaluation { + decision: Decision::Forbidden, + matched_rules: vec![RuleMatch::PrefixRuleMatch { + matched_prefix: vec!["rm".to_string()], + decision: Decision::Forbidden, + justification: None, + }], + } + ); + + Ok(()) + } + + #[tokio::test] + async fn merges_requirements_exec_policy_with_file_rules() -> anyhow::Result<()> { + let temp_dir = tempdir()?; + let policy_dir = temp_dir.path().join("rules"); + std::fs::create_dir_all(&policy_dir)?; + std::fs::write( + policy_dir.join("deny.rules"), + r#"prefix_rule(pattern=["rm"], decision="forbidden")"#, + )?; + + let requirements = requirements_from_toml( + r#" + [rules] + prefix_rules = [ + { pattern = [{ token = "git" }, { token = "push" }], decision = "prompt" }, + ] + "#, + ); + let config_stack = + config_stack_for_dot_codex_folder_with_requirements(temp_dir.path(), requirements); + + let policy = load_exec_policy(&config_stack).await?; + + assert_eq!( + policy.check_multiple([vec!["rm".to_string()]].iter(), &panic_if_called), + Evaluation { + decision: Decision::Forbidden, + matched_rules: vec![RuleMatch::PrefixRuleMatch { + matched_prefix: vec!["rm".to_string()], + decision: Decision::Forbidden, + justification: None, + }], + } + ); + assert_eq!( + policy.check_multiple( + [vec!["git".to_string(), "push".to_string()]].iter(), + &panic_if_called + ), + Evaluation { + decision: Decision::Prompt, + matched_rules: vec![RuleMatch::PrefixRuleMatch { + matched_prefix: vec!["git".to_string(), "push".to_string()], + decision: Decision::Prompt, + justification: None, + }], + } + ); + + Ok(()) + } } diff --git a/codex-rs/core/src/exec_policy.rs b/codex-rs/core/src/exec_policy.rs index 07a9ebceba..c0cb7f8fcf 100644 --- a/codex-rs/core/src/exec_policy.rs +++ b/codex-rs/core/src/exec_policy.rs @@ -280,7 +280,18 @@ pub async fn load_exec_policy(config_stack: &ConfigLayerStack) -> Result Date: Fri, 30 Jan 2026 10:14:46 -0800 Subject: [PATCH 009/139] plan mode: add TL;DR checkpoint and client behavior note (#10195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Tightens Plan Mode to encourage exploration-first behavior and more back-and-forth alignment. - Adds a required TL;DR checkpoint before drafting the full plan. - Clarifies client behavior that can cause premature “Implement this plan?” prompts. ## What changed - Require at least one targeted non-mutating exploration pass before the first user question. - Insert a TL;DR checkpoint between Phase 2 (intent) and Phase 3 (implementation). - TL;DR checkpoint guidance: - Label: “Proposed Plan (TL;DR)” - Format: 3–5 bullets using `- ` - Options: exactly one option, “Approve” - `isOther: true`, with explicit guidance that “None of the above” is the edit path in the current UI. - Require the final plan to include a TL;DR consistent with the approved checkpoint. ## Why - In Plan Mode, any normal assistant message at turn completion is treated as plan content by the client. This can trigger premature “Implement this plan?” prompts. - The TL;DR checkpoint aligns on direction before Codex drafts a long, decision-complete plan. ## Testing - Manual: built the local CLI and verified the flow now explores first, presents a TL;DR checkpoint, and only drafts the full plan after approval. --------- Co-authored-by: Nick Baumann <@openai.com> --- codex-rs/core/templates/collaboration_mode/plan.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codex-rs/core/templates/collaboration_mode/plan.md b/codex-rs/core/templates/collaboration_mode/plan.md index 4b5f450a50..cffbf16d36 100644 --- a/codex-rs/core/templates/collaboration_mode/plan.md +++ b/codex-rs/core/templates/collaboration_mode/plan.md @@ -37,6 +37,8 @@ When in doubt: if the action would reasonably be described as "doing the work" r Begin by grounding yourself in the actual environment. Eliminate unknowns in the prompt by discovering facts, not by asking the user. Resolve all questions that can be answered through exploration or inspection. Identify missing or ambiguous details only if they cannot be derived from the environment. Silent exploration between turns is allowed and encouraged. +Before asking the user any question, perform at least one targeted non-mutating exploration pass (for example: search relevant files, inspect likely entrypoints/configs, confirm current implementation shape), unless no local environment/repo is available. + Do not ask questions that can be answered from the repo or system (for example, "where is this struct?" or "which UI component should we use?" when exploration can make it clear). Only ask once you have exhausted reasonable non-mutating exploration. ## PHASE 2 — Intent chat (what they actually want) @@ -97,6 +99,7 @@ Only output the final plan when it is decision complete and leaves no decisions The final plan must be plan-only and include: * A clear title +* A TL;DR section (3–5 bullets) * Exact file paths to change * Exact structures or shapes to introduce or modify * Exact function, method, type, and variable names and signatures From 40bf11bd52422c3a5bf39fe8daae8999949874be Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 30 Jan 2026 10:29:54 -0800 Subject: [PATCH 010/139] chore: fix the build breakage that came from a merge race (#10239) I think I needed to rebase on top of https://github.com/openai/codex/pull/10167 before merging https://github.com/openai/codex/pull/10208. --- codex-rs/Cargo.lock | 1 - codex-rs/cloud-requirements/Cargo.toml | 1 - codex-rs/cloud-requirements/src/lib.rs | 5 +---- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 44f35f78ae..5cb338be1d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1298,7 +1298,6 @@ version = "0.0.0" dependencies = [ "async-trait", "base64", - "codex-app-server-protocol", "codex-backend-client", "codex-core", "codex-otel", diff --git a/codex-rs/cloud-requirements/Cargo.toml b/codex-rs/cloud-requirements/Cargo.toml index 2bfde660bf..071c98b9b4 100644 --- a/codex-rs/cloud-requirements/Cargo.toml +++ b/codex-rs/cloud-requirements/Cargo.toml @@ -9,7 +9,6 @@ workspace = true [dependencies] async-trait = { workspace = true } -codex-app-server-protocol = { workspace = true } codex-backend-client = { workspace = true } codex-core = { workspace = true } codex-otel = { workspace = true } diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 37faed081b..ed1bb051d5 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -8,7 +8,6 @@ //! requirements before Codex will run. use async_trait::async_trait; -use codex_app_server_protocol::AuthMode; use codex_backend_client::Client as BackendClient; use codex_core::AuthManager; use codex_core::auth::CodexAuth; @@ -120,9 +119,7 @@ impl CloudRequirementsService { async fn fetch(&self) -> Option { let auth = self.auth_manager.auth().await?; - if !(auth.mode == AuthMode::ChatGPT - && auth.account_plan_type() == Some(PlanType::Enterprise)) - { + if !(auth.is_chatgpt_auth() && auth.account_plan_type() == Some(PlanType::Enterprise)) { return None; } From ec4a2d07e411d11f4de13b4e09aa7386c914df6d Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Fri, 30 Jan 2026 10:59:30 -0800 Subject: [PATCH 011/139] Plan mode: stream proposed plans, emit plan items, and render in TUI (#9786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Stream proposed plans in Plan Mode using `` tags parsed in core, emitting plan deltas plus a plan `ThreadItem`, while stripping tags from normal assistant output. - Persist plan items and rebuild them on resume so proposed plans show in thread history. - Wire plan items/deltas through app-server protocol v2 and render a dedicated proposed-plan view in the TUI, including the “Implement this plan?” prompt only when a plan item is present. ## Changes ### Core (`codex-rs/core`) - Added a generic, line-based tag parser that buffers each line until it can disprove a tag prefix; implements auto-close on `finish()` for unterminated tags. `codex-rs/core/src/tagged_block_parser.rs` - Refactored proposed plan parsing to wrap the generic parser. `codex-rs/core/src/proposed_plan_parser.rs` - In plan mode, stream assistant deltas as: - **Normal text** → `AgentMessageContentDelta` - **Plan text** → `PlanDelta` + `TurnItem::Plan` start/completion (`codex-rs/core/src/codex.rs`) - Final plan item content is derived from the completed assistant message (authoritative), not necessarily the concatenated deltas. - Strips `` blocks from assistant text in plan mode so tags don’t appear in normal messages. (`codex-rs/core/src/stream_events_utils.rs`) - Persist `ItemCompleted` events only for plan items for rollout replay. (`codex-rs/core/src/rollout/policy.rs`) - Guard `update_plan` tool in Plan Mode with a clear error message. (`codex-rs/core/src/tools/handlers/plan.rs`) - Updated Plan Mode prompt to: - keep `` out of non-final reasoning/preambles - require exact tag formatting - allow only one `` block per turn (`codex-rs/core/templates/collaboration_mode/plan.md`) ### Protocol / App-server protocol - Added `TurnItem::Plan` and `PlanDeltaEvent` to core protocol items. (`codex-rs/protocol/src/items.rs`, `codex-rs/protocol/src/protocol.rs`) - Added v2 `ThreadItem::Plan` and `PlanDeltaNotification` with EXPERIMENTAL markers and note that deltas may not match the final plan item. (`codex-rs/app-server-protocol/src/protocol/v2.rs`) - Added plan delta route in app-server protocol common mapping. (`codex-rs/app-server-protocol/src/protocol/common.rs`) - Rebuild plan items from persisted `ItemCompleted` events on resume. (`codex-rs/app-server-protocol/src/protocol/thread_history.rs`) ### App-server - Forward plan deltas to v2 clients and map core plan items to v2 plan items. (`codex-rs/app-server/src/bespoke_event_handling.rs`, `codex-rs/app-server/src/codex_message_processor.rs`) - Added v2 plan item tests. (`codex-rs/app-server/tests/suite/v2/plan_item.rs`) ### TUI - Added a dedicated proposed plan history cell with special background and padding, and moved “• Proposed Plan” outside the highlighted block. (`codex-rs/tui/src/history_cell.rs`, `codex-rs/tui/src/style.rs`) - Only show “Implement this plan?” when a plan item exists. (`codex-rs/tui/src/chatwidget.rs`, `codex-rs/tui/src/chatwidget/tests.rs`) Screenshot 2026-01-29 at 7 06 24 PM ### Docs / Misc - Updated protocol docs to mention plan deltas. (`codex-rs/docs/protocol_v1.md`) - Minor plumbing updates in exec/debug clients to tolerate plan deltas. (`codex-rs/debug-client/src/reader.rs`, `codex-rs/exec/...`) ## Tests - Added core integration tests: - Plan mode strips plan from agent messages. - Missing `` closes at end-of-message. (`codex-rs/core/tests/suite/items.rs`) - Added unit tests for generic tag parser (prefix buffering, non-tag lines, auto-close). (`codex-rs/core/src/tagged_block_parser.rs`) - Existing app-server plan item tests in v2. (`codex-rs/app-server/tests/suite/v2/plan_item.rs`) ## Notes / Behavior - Plan output no longer appears in standard assistant text in Plan Mode; it streams via `PlanDelta` and completes as a `TurnItem::Plan`. - The final plan item content is authoritative and may diverge from streamed deltas (documented as experimental). - Reasoning summaries are not filtered; prompt instructs the model not to include `` outside the final plan message. ## Codex Author `codex fork 019bec2d-b09d-7450-b292-d7bcdddcdbfb` --- .../src/protocol/common.rs | 2 + .../src/protocol/thread_history.rs | 15 + .../app-server-protocol/src/protocol/v2.rs | 21 + codex-rs/app-server/README.md | 5 + .../app-server/src/bespoke_event_handling.rs | 18 +- .../app-server/tests/common/mcp_process.rs | 4 + codex-rs/app-server/tests/suite/v2/mod.rs | 1 + .../app-server/tests/suite/v2/plan_item.rs | 257 ++++++++++ codex-rs/core/src/agent/control.rs | 2 + codex-rs/core/src/codex.rs | 461 +++++++++++++++++- codex-rs/core/src/compact.rs | 1 + codex-rs/core/src/compact_remote.rs | 1 + codex-rs/core/src/lib.rs | 2 + codex-rs/core/src/proposed_plan_parser.rs | 185 +++++++ codex-rs/core/src/rollout/policy.rs | 8 +- codex-rs/core/src/stream_events_utils.rs | 54 +- codex-rs/core/src/tagged_block_parser.rs | 314 ++++++++++++ codex-rs/core/src/tasks/user_shell.rs | 1 + codex-rs/core/src/tools/handlers/plan.rs | 6 + .../core/templates/collaboration_mode/plan.md | 26 +- codex-rs/core/tests/suite/items.rs | 267 ++++++++++ codex-rs/debug-client/src/reader.rs | 5 + codex-rs/docs/protocol_v1.md | 5 +- .../src/event_processor_with_human_output.rs | 18 +- .../src/event_processor_with_jsonl_output.rs | 14 +- .../tests/event_processor_with_json_output.rs | 2 + codex-rs/mcp-server/src/codex_tool_runner.rs | 3 + codex-rs/protocol/src/config_types.rs | 5 +- codex-rs/protocol/src/items.rs | 9 + codex-rs/protocol/src/plan_tool.rs | 1 + codex-rs/protocol/src/protocol.rs | 12 + codex-rs/tui/src/chatwidget.rs | 114 ++++- codex-rs/tui/src/chatwidget/tests.rs | 42 +- codex-rs/tui/src/history_cell.rs | 58 +++ codex-rs/tui/src/streaming/controller.rs | 103 ++++ codex-rs/tui/src/style.rs | 21 + 36 files changed, 2021 insertions(+), 42 deletions(-) create mode 100644 codex-rs/app-server/tests/suite/v2/plan_item.rs create mode 100644 codex-rs/core/src/proposed_plan_parser.rs create mode 100644 codex-rs/core/src/tagged_block_parser.rs diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 67736374ec..efcc651b33 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -609,6 +609,8 @@ server_notification_definitions! { /// This event is internal-only. Used by Codex Cloud. RawResponseItemCompleted => "rawResponseItem/completed" (v2::RawResponseItemCompletedNotification), AgentMessageDelta => "item/agentMessage/delta" (v2::AgentMessageDeltaNotification), + /// EXPERIMENTAL - proposed plan streaming deltas for plan items. + PlanDelta => "item/plan/delta" (v2::PlanDeltaNotification), CommandExecutionOutputDelta => "item/commandExecution/outputDelta" (v2::CommandExecutionOutputDeltaNotification), TerminalInteraction => "item/commandExecution/terminalInteraction" (v2::TerminalInteractionNotification), FileChangeOutputDelta => "item/fileChange/outputDelta" (v2::FileChangeOutputDeltaNotification), diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index e6c679b418..39efe47655 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -6,6 +6,7 @@ use crate::protocol::v2::UserInput; use codex_protocol::protocol::AgentReasoningEvent; use codex_protocol::protocol::AgentReasoningRawContentEvent; use codex_protocol::protocol::EventMsg; +use codex_protocol::protocol::ItemCompletedEvent; use codex_protocol::protocol::ThreadRolledBackEvent; use codex_protocol::protocol::TurnAbortedEvent; use codex_protocol::protocol::UserMessageEvent; @@ -55,6 +56,7 @@ impl ThreadHistoryBuilder { EventMsg::AgentReasoningRawContent(payload) => { self.handle_agent_reasoning_raw_content(payload) } + EventMsg::ItemCompleted(payload) => self.handle_item_completed(payload), EventMsg::TokenCount(_) => {} EventMsg::EnteredReviewMode(_) => {} EventMsg::ExitedReviewMode(_) => {} @@ -125,6 +127,19 @@ impl ThreadHistoryBuilder { }); } + fn handle_item_completed(&mut self, payload: &ItemCompletedEvent) { + if let codex_protocol::items::TurnItem::Plan(plan) = &payload.item { + if plan.text.is_empty() { + return; + } + let id = self.next_item_id(); + self.ensure_turn().items.push(ThreadItem::Plan { + id, + text: plan.text.clone(), + }); + } + } + fn handle_turn_aborted(&mut self, _payload: &TurnAbortedEvent) { let Some(turn) = self.current_turn.as_mut() else { return; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 8bea094487..8164eb5289 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2036,6 +2036,11 @@ pub enum ThreadItem { AgentMessage { id: String, text: String }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] + /// EXPERIMENTAL - proposed plan item content. The completed plan item is + /// authoritative and may not match the concatenation of `PlanDelta` text. + Plan { id: String, text: String }, + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] Reasoning { id: String, #[serde(default)] @@ -2140,6 +2145,10 @@ impl From for ThreadItem { .collect::(); ThreadItem::AgentMessage { id: agent.id, text } } + CoreTurnItem::Plan(plan) => ThreadItem::Plan { + id: plan.id, + text: plan.text, + }, CoreTurnItem::Reasoning(reasoning) => ThreadItem::Reasoning { id: reasoning.id, summary: reasoning.summary_text, @@ -2428,6 +2437,18 @@ pub struct AgentMessageDeltaNotification { pub delta: String, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +/// EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should +/// not assume concatenated deltas match the completed plan item content. +pub struct PlanDeltaNotification { + pub thread_id: String, + pub turn_id: String, + pub item_id: String, + pub delta: String, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 3798d76027..76d1131a2e 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -444,6 +444,7 @@ Today both notifications carry an empty `items` array even when item events were - `userMessage` — `{id, content}` where `content` is a list of user inputs (`text`, `image`, or `localImage`). - `agentMessage` — `{id, text}` containing the accumulated agent reply. +- `plan` — `{id, text}` emitted for plan-mode turns; plan text can stream via `item/plan/delta` (experimental). - `reasoning` — `{id, summary, content}` where `summary` holds streamed reasoning summaries (applicable for most OpenAI models) and `content` holds raw reasoning blocks (applicable for e.g. open source models). - `commandExecution` — `{id, command, cwd, status, commandActions, aggregatedOutput?, exitCode?, durationMs?}` for sandboxed commands; `status` is `inProgress`, `completed`, `failed`, or `declined`. - `fileChange` — `{id, changes, status}` describing proposed edits; `changes` list `{path, kind, diff}` and `status` is `inProgress`, `completed`, `failed`, or `declined`. @@ -467,6 +468,10 @@ There are additional item-specific events: - `item/agentMessage/delta` — appends streamed text for the agent message; concatenate `delta` values for the same `itemId` in order to reconstruct the full reply. +#### plan + +- `item/plan/delta` — streams proposed plan content for plan items (experimental); concatenate `delta` values for the same plan `itemId`. These deltas correspond to the `` block. + #### reasoning - `item/reasoning/summaryTextDelta` — streams readable reasoning summaries; `summaryIndex` increments when a new summary section opens. diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index dd2628deb7..bf8fad3a9c 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -44,6 +44,7 @@ use codex_app_server_protocol::McpToolCallResult; use codex_app_server_protocol::McpToolCallStatus; use codex_app_server_protocol::PatchApplyStatus; use codex_app_server_protocol::PatchChangeKind as V2PatchChangeKind; +use codex_app_server_protocol::PlanDeltaNotification; use codex_app_server_protocol::RawResponseItemCompletedNotification; use codex_app_server_protocol::ReasoningSummaryPartAddedNotification; use codex_app_server_protocol::ReasoningSummaryTextDeltaNotification; @@ -118,6 +119,7 @@ pub(crate) async fn apply_bespoke_event_handling( msg, } = event; match msg { + EventMsg::TurnStarted(_) => {} EventMsg::TurnComplete(_ev) => { handle_turn_complete( conversation_id, @@ -593,14 +595,27 @@ pub(crate) async fn apply_bespoke_event_handling( .await; } EventMsg::AgentMessageContentDelta(event) => { + let codex_protocol::protocol::AgentMessageContentDeltaEvent { item_id, delta, .. } = + event; let notification = AgentMessageDeltaNotification { + thread_id: conversation_id.to_string(), + turn_id: event_turn_id.clone(), + item_id, + delta, + }; + outgoing + .send_server_notification(ServerNotification::AgentMessageDelta(notification)) + .await; + } + EventMsg::PlanDelta(event) => { + let notification = PlanDeltaNotification { thread_id: conversation_id.to_string(), turn_id: event_turn_id.clone(), item_id: event.item_id, delta: event.delta, }; outgoing - .send_server_notification(ServerNotification::AgentMessageDelta(notification)) + .send_server_notification(ServerNotification::PlanDelta(notification)) .await; } EventMsg::ContextCompacted(..) => { @@ -1160,6 +1175,7 @@ async fn handle_turn_plan_update( api_version: ApiVersion, outgoing: &OutgoingMessageSender, ) { + // `update_plan` is a todo/checklist tool; it is not related to plan-mode updates if let ApiVersion::V2 = api_version { let notification = TurnPlanUpdatedNotification { thread_id: conversation_id.to_string(), diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 4eeab8f950..90cd7635a3 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -736,6 +736,10 @@ impl McpProcess { Ok(notification) } + pub async fn read_next_message(&mut self) -> anyhow::Result { + self.read_stream_until_message(|_| true).await + } + /// Clears any buffered messages so future reads only consider new stream items. /// /// We call this when e.g. we want to validate against the next turn and no longer care about diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 8f54753bf6..b67c3e5c33 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -8,6 +8,7 @@ mod dynamic_tools; mod initialize; mod model_list; mod output_schema; +mod plan_item; mod rate_limits; mod request_user_input; mod review; diff --git a/codex-rs/app-server/tests/suite/v2/plan_item.rs b/codex-rs/app-server/tests/suite/v2/plan_item.rs new file mode 100644 index 0000000000..d138954ac4 --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/plan_item.rs @@ -0,0 +1,257 @@ +use anyhow::Result; +use anyhow::anyhow; +use app_test_support::McpProcess; +use app_test_support::create_mock_responses_server_sequence; +use app_test_support::to_response; +use codex_app_server_protocol::ItemCompletedNotification; +use codex_app_server_protocol::ItemStartedNotification; +use codex_app_server_protocol::JSONRPCMessage; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::PlanDeltaNotification; +use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ThreadItem; +use codex_app_server_protocol::ThreadStartParams; +use codex_app_server_protocol::ThreadStartResponse; +use codex_app_server_protocol::TurnCompletedNotification; +use codex_app_server_protocol::TurnStartParams; +use codex_app_server_protocol::TurnStartResponse; +use codex_app_server_protocol::TurnStatus; +use codex_app_server_protocol::UserInput as V2UserInput; +use codex_core::features::FEATURES; +use codex_core::features::Feature; +use codex_protocol::config_types::CollaborationMode; +use codex_protocol::config_types::ModeKind; +use codex_protocol::config_types::Settings; +use core_test_support::responses; +use core_test_support::skip_if_no_network; +use pretty_assertions::assert_eq; +use std::collections::BTreeMap; +use std::path::Path; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + +#[tokio::test] +async fn plan_mode_uses_proposed_plan_block_for_plan_item() -> Result<()> { + skip_if_no_network!(Ok(())); + + let plan_block = "\n# Final plan\n- first\n- second\n\n"; + let full_message = format!("Preface\n{plan_block}Postscript"); + let responses = vec![responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_message_item_added("msg-1", ""), + responses::ev_output_text_delta(&full_message), + responses::ev_assistant_message("msg-1", &full_message), + responses::ev_completed("resp-1"), + ])]; + let server = create_mock_responses_server_sequence(responses).await; + + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let turn = start_plan_mode_turn(&mut mcp).await?; + let (_, completed_items, plan_deltas, turn_completed) = + collect_turn_notifications(&mut mcp).await?; + + assert_eq!(turn_completed.turn.id, turn.id); + assert_eq!(turn_completed.turn.status, TurnStatus::Completed); + + let expected_plan = ThreadItem::Plan { + id: format!("{}-plan", turn.id), + text: "# Final plan\n- first\n- second\n".to_string(), + }; + let expected_plan_id = format!("{}-plan", turn.id); + let streamed_plan = plan_deltas + .iter() + .map(|delta| delta.delta.as_str()) + .collect::(); + assert_eq!(streamed_plan, "# Final plan\n- first\n- second\n"); + assert!( + plan_deltas + .iter() + .all(|delta| delta.item_id == expected_plan_id) + ); + let plan_items = completed_items + .iter() + .filter_map(|item| match item { + ThreadItem::Plan { .. } => Some(item.clone()), + _ => None, + }) + .collect::>(); + assert_eq!(plan_items, vec![expected_plan]); + assert!( + completed_items + .iter() + .any(|item| matches!(item, ThreadItem::AgentMessage { .. })), + "agent message items should still be emitted alongside the plan item" + ); + + Ok(()) +} + +#[tokio::test] +async fn plan_mode_without_proposed_plan_does_not_emit_plan_item() -> Result<()> { + skip_if_no_network!(Ok(())); + + let responses = vec![responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_assistant_message("msg-1", "Done"), + responses::ev_completed("resp-1"), + ])]; + let server = create_mock_responses_server_sequence(responses).await; + + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let _turn = start_plan_mode_turn(&mut mcp).await?; + let (_, completed_items, plan_deltas, _) = collect_turn_notifications(&mut mcp).await?; + + let has_plan_item = completed_items + .iter() + .any(|item| matches!(item, ThreadItem::Plan { .. })); + assert!(!has_plan_item); + assert!(plan_deltas.is_empty()); + + Ok(()) +} + +async fn start_plan_mode_turn(mcp: &mut McpProcess) -> Result { + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let thread = to_response::(thread_resp)?.thread; + + let collaboration_mode = CollaborationMode { + mode: ModeKind::Plan, + settings: Settings { + model: "mock-model".to_string(), + reasoning_effort: None, + developer_instructions: None, + }, + }; + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id, + input: vec![V2UserInput::Text { + text: "Plan this".to_string(), + text_elements: Vec::new(), + }], + collaboration_mode: Some(collaboration_mode), + ..Default::default() + }) + .await?; + let turn_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + Ok(to_response::(turn_resp)?.turn) +} + +async fn collect_turn_notifications( + mcp: &mut McpProcess, +) -> Result<( + Vec, + Vec, + Vec, + TurnCompletedNotification, +)> { + let mut started_items = Vec::new(); + let mut completed_items = Vec::new(); + let mut plan_deltas = Vec::new(); + + loop { + let message = timeout(DEFAULT_READ_TIMEOUT, mcp.read_next_message()).await??; + let JSONRPCMessage::Notification(notification) = message else { + continue; + }; + match notification.method.as_str() { + "item/started" => { + let params = notification + .params + .ok_or_else(|| anyhow!("item/started notifications must include params"))?; + let payload: ItemStartedNotification = serde_json::from_value(params)?; + started_items.push(payload.item); + } + "item/completed" => { + let params = notification + .params + .ok_or_else(|| anyhow!("item/completed notifications must include params"))?; + let payload: ItemCompletedNotification = serde_json::from_value(params)?; + completed_items.push(payload.item); + } + "item/plan/delta" => { + let params = notification + .params + .ok_or_else(|| anyhow!("item/plan/delta notifications must include params"))?; + let payload: PlanDeltaNotification = serde_json::from_value(params)?; + plan_deltas.push(payload); + } + "turn/completed" => { + let params = notification + .params + .ok_or_else(|| anyhow!("turn/completed notifications must include params"))?; + let payload: TurnCompletedNotification = serde_json::from_value(params)?; + return Ok((started_items, completed_items, plan_deltas, payload)); + } + _ => {} + } + } +} + +fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { + let features = BTreeMap::from([ + (Feature::RemoteModels, false), + (Feature::CollaborationModes, true), + ]); + let feature_entries = features + .into_iter() + .map(|(feature, enabled)| { + let key = FEATURES + .iter() + .find(|spec| spec.id == feature) + .map(|spec| spec.key) + .unwrap_or_else(|| panic!("missing feature key for {feature:?}")); + format!("{key} = {enabled}") + }) + .collect::>() + .join("\n"); + let config_toml = codex_home.join("config.toml"); + std::fs::write( + config_toml, + format!( + r#" +model = "mock-model" +approval_policy = "never" +sandbox_mode = "read-only" + +model_provider = "mock_provider" + +[features] +{feature_entries} + +[model_providers.mock_provider] +name = "Mock provider for test" +base_url = "{server_uri}/v1" +wire_api = "responses" +request_max_retries = 0 +stream_max_retries = 0 +"# + ), + ) +} diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 611c0d164c..151c6fa0fb 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -146,6 +146,7 @@ mod tests { use crate::config::Config; use crate::config::ConfigBuilder; use assert_matches::assert_matches; + use codex_protocol::config_types::ModeKind; use codex_protocol::protocol::ErrorEvent; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::TurnAbortReason; @@ -231,6 +232,7 @@ mod tests { async fn on_event_updates_status_from_task_started() { let status = agent_status_from_event(&EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, })); assert_eq!(status, Some(AgentStatus::Running)); } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 324f41a04e..30dd8d24fc 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -30,6 +30,7 @@ use crate::rollout::session_index; use crate::stream_events_utils::HandleOutputCtx; use crate::stream_events_utils::handle_non_tool_response_item; use crate::stream_events_utils::handle_output_item_done; +use crate::stream_events_utils::last_assistant_message_from_item; use crate::terminal; use crate::transport_manager::TransportManager; use crate::truncate::TruncationPolicy; @@ -44,6 +45,7 @@ use codex_protocol::config_types::Settings; use codex_protocol::config_types::WebSearchMode; use codex_protocol::dynamic_tools::DynamicToolResponse; use codex_protocol::dynamic_tools::DynamicToolSpec; +use codex_protocol::items::PlanItem; use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; use codex_protocol::models::BaseInstructions; @@ -127,6 +129,9 @@ use crate::mentions::collect_explicit_app_paths; use crate::mentions::collect_tool_mentions_from_messages; use crate::model_provider_info::CHAT_WIRE_API_DEPRECATION_SUMMARY; use crate::project_doc::get_user_instructions; +use crate::proposed_plan_parser::ProposedPlanParser; +use crate::proposed_plan_parser::ProposedPlanSegment; +use crate::proposed_plan_parser::extract_proposed_plan_text; use crate::protocol::AgentMessageContentDeltaEvent; use crate::protocol::AgentReasoningSectionBreakEvent; use crate::protocol::ApplyPatchApprovalRequestEvent; @@ -139,6 +144,7 @@ use crate::protocol::EventMsg; use crate::protocol::ExecApprovalRequestEvent; use crate::protocol::McpServerRefreshConfig; use crate::protocol::Op; +use crate::protocol::PlanDeltaEvent; use crate::protocol::RateLimitSnapshot; use crate::protocol::ReasoningContentDeltaEvent; use crate::protocol::ReasoningRawContentDeltaEvent; @@ -482,6 +488,7 @@ pub(crate) struct TurnContext { pub(crate) developer_instructions: Option, pub(crate) compact_prompt: Option, pub(crate) user_instructions: Option, + pub(crate) collaboration_mode_kind: ModeKind, pub(crate) personality: Option, pub(crate) approval_policy: AskForApproval, pub(crate) sandbox_policy: SandboxPolicy, @@ -682,6 +689,7 @@ impl Session { developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), user_instructions: session_configuration.user_instructions.clone(), + collaboration_mode_kind: session_configuration.collaboration_mode.mode, personality: session_configuration.personality, approval_policy: session_configuration.approval_policy.value(), sandbox_policy: session_configuration.sandbox_policy.get().clone(), @@ -3196,6 +3204,7 @@ async fn spawn_review_thread( developer_instructions: None, user_instructions: None, compact_prompt: parent_turn_context.compact_prompt.clone(), + collaboration_mode_kind: parent_turn_context.collaboration_mode_kind, personality: parent_turn_context.personality, approval_policy: parent_turn_context.approval_policy, sandbox_policy: parent_turn_context.sandbox_policy.clone(), @@ -3310,6 +3319,7 @@ pub(crate) async fn run_turn( let total_usage_tokens = sess.get_total_token_usage().await; let event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), + collaboration_mode_kind: turn_context.collaboration_mode_kind, }); sess.send_event(&turn_context, event).await; if total_usage_tokens >= auto_compact_limit { @@ -3759,6 +3769,381 @@ struct SamplingRequestResult { last_agent_message: Option, } +/// Ephemeral per-response state for streaming a single proposed plan. +/// This is intentionally not persisted or stored in session/state since it +/// only exists while a response is actively streaming. The final plan text +/// is extracted from the completed assistant message. +/// Tracks a single proposed plan item across a streaming response. +struct ProposedPlanItemState { + item_id: String, + started: bool, + completed: bool, +} + +/// Per-item plan parsers so we can buffer text while detecting `` +/// tags without ever mixing buffered lines across item ids. +struct PlanParsers { + assistant: HashMap, +} + +impl PlanParsers { + fn new() -> Self { + Self { + assistant: HashMap::new(), + } + } + + fn assistant_parser_mut(&mut self, item_id: &str) -> &mut ProposedPlanParser { + self.assistant + .entry(item_id.to_string()) + .or_insert_with(ProposedPlanParser::new) + } + + fn take_assistant_parser(&mut self, item_id: &str) -> Option { + self.assistant.remove(item_id) + } + + fn drain_assistant_parsers(&mut self) -> Vec<(String, ProposedPlanParser)> { + self.assistant.drain().collect() + } +} + +/// Aggregated state used only while streaming a plan-mode response. +/// Includes per-item parsers, deferred agent message bookkeeping, and the plan item lifecycle. +struct PlanModeStreamState { + /// Per-item parsers for assistant streams in plan mode. + plan_parsers: PlanParsers, + /// Agent message items started by the model but deferred until we see non-plan text. + pending_agent_message_items: HashMap, + /// Agent message items whose start notification has been emitted. + started_agent_message_items: HashSet, + /// Leading whitespace buffered until we see non-whitespace text for an item. + leading_whitespace_by_item: HashMap, + /// Tracks plan item lifecycle while streaming plan output. + plan_item_state: ProposedPlanItemState, +} + +impl PlanModeStreamState { + fn new(turn_id: &str) -> Self { + Self { + plan_parsers: PlanParsers::new(), + pending_agent_message_items: HashMap::new(), + started_agent_message_items: HashSet::new(), + leading_whitespace_by_item: HashMap::new(), + plan_item_state: ProposedPlanItemState::new(turn_id), + } + } +} + +impl ProposedPlanItemState { + fn new(turn_id: &str) -> Self { + Self { + item_id: format!("{turn_id}-plan"), + started: false, + completed: false, + } + } + + async fn start(&mut self, sess: &Session, turn_context: &TurnContext) { + if self.started || self.completed { + return; + } + self.started = true; + let item = TurnItem::Plan(PlanItem { + id: self.item_id.clone(), + text: String::new(), + }); + sess.emit_turn_item_started(turn_context, &item).await; + } + + async fn push_delta(&mut self, sess: &Session, turn_context: &TurnContext, delta: &str) { + if self.completed { + return; + } + if delta.is_empty() { + return; + } + let event = PlanDeltaEvent { + thread_id: sess.conversation_id.to_string(), + turn_id: turn_context.sub_id.clone(), + item_id: self.item_id.clone(), + delta: delta.to_string(), + }; + sess.send_event(turn_context, EventMsg::PlanDelta(event)) + .await; + } + + async fn complete_with_text( + &mut self, + sess: &Session, + turn_context: &TurnContext, + text: String, + ) { + if self.completed || !self.started { + return; + } + self.completed = true; + let item = TurnItem::Plan(PlanItem { + id: self.item_id.clone(), + text, + }); + sess.emit_turn_item_completed(turn_context, item).await; + } +} + +/// In plan mode we defer agent message starts until the parser emits non-plan +/// text. The parser buffers each line until it can rule out a tag prefix, so +/// plan-only outputs never show up as empty assistant messages. +async fn maybe_emit_pending_agent_message_start( + sess: &Session, + turn_context: &TurnContext, + state: &mut PlanModeStreamState, + item_id: &str, +) { + if state.started_agent_message_items.contains(item_id) { + return; + } + if let Some(item) = state.pending_agent_message_items.remove(item_id) { + sess.emit_turn_item_started(turn_context, &item).await; + state + .started_agent_message_items + .insert(item_id.to_string()); + } +} + +/// Agent messages are text-only today; concatenate all text entries. +fn agent_message_text(item: &codex_protocol::items::AgentMessageItem) -> String { + item.content + .iter() + .map(|entry| match entry { + codex_protocol::items::AgentMessageContent::Text { text } => text.as_str(), + }) + .collect() +} + +/// Split the stream into normal assistant text vs. proposed plan content. +/// Normal text becomes AgentMessage deltas; plan content becomes PlanDelta + +/// TurnItem::Plan. +async fn handle_plan_segments( + sess: &Session, + turn_context: &TurnContext, + state: &mut PlanModeStreamState, + item_id: &str, + segments: Vec, +) { + for segment in segments { + match segment { + ProposedPlanSegment::Normal(delta) => { + if delta.is_empty() { + continue; + } + let has_non_whitespace = delta.chars().any(|ch| !ch.is_whitespace()); + if !has_non_whitespace && !state.started_agent_message_items.contains(item_id) { + let entry = state + .leading_whitespace_by_item + .entry(item_id.to_string()) + .or_default(); + entry.push_str(&delta); + continue; + } + let delta = if !state.started_agent_message_items.contains(item_id) { + if let Some(prefix) = state.leading_whitespace_by_item.remove(item_id) { + format!("{prefix}{delta}") + } else { + delta + } + } else { + delta + }; + maybe_emit_pending_agent_message_start(sess, turn_context, state, item_id).await; + + let event = AgentMessageContentDeltaEvent { + thread_id: sess.conversation_id.to_string(), + turn_id: turn_context.sub_id.clone(), + item_id: item_id.to_string(), + delta, + }; + sess.send_event(turn_context, EventMsg::AgentMessageContentDelta(event)) + .await; + } + ProposedPlanSegment::ProposedPlanStart => { + if !state.plan_item_state.completed { + state.plan_item_state.start(sess, turn_context).await; + } + } + ProposedPlanSegment::ProposedPlanDelta(delta) => { + if !state.plan_item_state.completed { + if !state.plan_item_state.started { + state.plan_item_state.start(sess, turn_context).await; + } + state + .plan_item_state + .push_delta(sess, turn_context, &delta) + .await; + } + } + ProposedPlanSegment::ProposedPlanEnd => {} + } + } +} + +/// Flush any buffered proposed-plan segments when a specific assistant message ends. +async fn flush_proposed_plan_segments_for_item( + sess: &Session, + turn_context: &TurnContext, + state: &mut PlanModeStreamState, + item_id: &str, +) { + let Some(mut parser) = state.plan_parsers.take_assistant_parser(item_id) else { + return; + }; + let segments = parser.finish(); + if segments.is_empty() { + return; + } + handle_plan_segments(sess, turn_context, state, item_id, segments).await; +} + +/// Flush any remaining assistant plan parsers when the response completes. +async fn flush_proposed_plan_segments_all( + sess: &Session, + turn_context: &TurnContext, + state: &mut PlanModeStreamState, +) { + for (item_id, mut parser) in state.plan_parsers.drain_assistant_parsers() { + let segments = parser.finish(); + if segments.is_empty() { + continue; + } + handle_plan_segments(sess, turn_context, state, &item_id, segments).await; + } +} + +/// Emit completion for plan items by parsing the finalized assistant message. +async fn maybe_complete_plan_item_from_message( + sess: &Session, + turn_context: &TurnContext, + state: &mut PlanModeStreamState, + item: &ResponseItem, +) { + if let ResponseItem::Message { role, content, .. } = item + && role == "assistant" + { + let mut text = String::new(); + for entry in content { + if let ContentItem::OutputText { text: chunk } = entry { + text.push_str(chunk); + } + } + if let Some(plan_text) = extract_proposed_plan_text(&text) { + if !state.plan_item_state.started { + state.plan_item_state.start(sess, turn_context).await; + } + state + .plan_item_state + .complete_with_text(sess, turn_context, plan_text) + .await; + } + } +} + +/// Emit a completed agent message in plan mode, respecting deferred starts. +async fn emit_agent_message_in_plan_mode( + sess: &Session, + turn_context: &TurnContext, + agent_message: codex_protocol::items::AgentMessageItem, + state: &mut PlanModeStreamState, +) { + let agent_message_id = agent_message.id.clone(); + let text = agent_message_text(&agent_message); + if text.trim().is_empty() { + state.pending_agent_message_items.remove(&agent_message_id); + state.started_agent_message_items.remove(&agent_message_id); + return; + } + + maybe_emit_pending_agent_message_start(sess, turn_context, state, &agent_message_id).await; + + if !state + .started_agent_message_items + .contains(&agent_message_id) + { + let start_item = state + .pending_agent_message_items + .remove(&agent_message_id) + .unwrap_or_else(|| { + TurnItem::AgentMessage(codex_protocol::items::AgentMessageItem { + id: agent_message_id.clone(), + content: Vec::new(), + }) + }); + sess.emit_turn_item_started(turn_context, &start_item).await; + state + .started_agent_message_items + .insert(agent_message_id.clone()); + } + + sess.emit_turn_item_completed(turn_context, TurnItem::AgentMessage(agent_message)) + .await; + state.started_agent_message_items.remove(&agent_message_id); +} + +/// Emit completion for a plan-mode turn item, handling agent messages specially. +async fn emit_turn_item_in_plan_mode( + sess: &Session, + turn_context: &TurnContext, + turn_item: TurnItem, + previously_active_item: Option<&TurnItem>, + state: &mut PlanModeStreamState, +) { + match turn_item { + TurnItem::AgentMessage(agent_message) => { + emit_agent_message_in_plan_mode(sess, turn_context, agent_message, state).await; + } + _ => { + if previously_active_item.is_none() { + sess.emit_turn_item_started(turn_context, &turn_item).await; + } + sess.emit_turn_item_completed(turn_context, turn_item).await; + } + } +} + +/// Handle a completed assistant response item in plan mode, returning true if handled. +async fn handle_assistant_item_done_in_plan_mode( + sess: &Session, + turn_context: &TurnContext, + item: &ResponseItem, + state: &mut PlanModeStreamState, + previously_active_item: Option<&TurnItem>, + last_agent_message: &mut Option, +) -> bool { + if let ResponseItem::Message { role, .. } = item + && role == "assistant" + { + maybe_complete_plan_item_from_message(sess, turn_context, state, item).await; + + if let Some(turn_item) = handle_non_tool_response_item(item, true).await { + emit_turn_item_in_plan_mode( + sess, + turn_context, + turn_item, + previously_active_item, + state, + ) + .await; + } + + sess.record_conversation_items(turn_context, std::slice::from_ref(item)) + .await; + if let Some(agent_message) = last_assistant_message_from_item(item, true) { + *last_agent_message = Some(agent_message); + } + return true; + } + false +} + async fn drain_in_flight( in_flight: &mut FuturesOrdered>>, sess: Arc, @@ -3795,10 +4180,6 @@ async fn try_run_sampling_request( prompt: &Prompt, cancellation_token: CancellationToken, ) -> CodexResult { - // TODO: If we need to guarantee the persisted mode always matches the prompt used for this - // turn, capture it in TurnContext at creation time. Using SessionConfiguration here avoids - // duplicating model settings on TurnContext, but a later Op could update the session config - // before this write occurs. let collaboration_mode = sess.current_collaboration_mode().await; let rollout_item = RolloutItem::TurnContext(TurnContextItem { cwd: turn_context.cwd.clone(), @@ -3843,6 +4224,8 @@ async fn try_run_sampling_request( let mut last_agent_message: Option = None; let mut active_item: Option = None; let mut should_emit_turn_diff = false; + let plan_mode = turn_context.collaboration_mode_kind == ModeKind::Plan; + let mut plan_mode_state = plan_mode.then(|| PlanModeStreamState::new(&turn_context.sub_id)); let receiving_span = trace_span!("receiving_stream"); let outcome: CodexResult = loop { let handle_responses = trace_span!( @@ -3881,6 +4264,33 @@ async fn try_run_sampling_request( ResponseEvent::Created => {} ResponseEvent::OutputItemDone(item) => { let previously_active_item = active_item.take(); + if let Some(state) = plan_mode_state.as_mut() { + if let Some(previous) = previously_active_item.as_ref() { + let item_id = previous.id(); + if matches!(previous, TurnItem::AgentMessage(_)) { + flush_proposed_plan_segments_for_item( + &sess, + &turn_context, + state, + &item_id, + ) + .await; + } + } + if handle_assistant_item_done_in_plan_mode( + &sess, + &turn_context, + &item, + state, + previously_active_item.as_ref(), + &mut last_agent_message, + ) + .await + { + continue; + } + } + let mut ctx = HandleOutputCtx { sess: sess.clone(), turn_context: turn_context.clone(), @@ -3900,8 +4310,17 @@ async fn try_run_sampling_request( needs_follow_up |= output_result.needs_follow_up; } ResponseEvent::OutputItemAdded(item) => { - if let Some(turn_item) = handle_non_tool_response_item(&item).await { - sess.emit_turn_item_started(&turn_context, &turn_item).await; + if let Some(turn_item) = handle_non_tool_response_item(&item, plan_mode).await { + if let Some(state) = plan_mode_state.as_mut() + && matches!(turn_item, TurnItem::AgentMessage(_)) + { + let item_id = turn_item.id(); + state + .pending_agent_message_items + .insert(item_id, turn_item.clone()); + } else { + sess.emit_turn_item_started(&turn_context, &turn_item).await; + } active_item = Some(turn_item); } } @@ -3925,6 +4344,9 @@ async fn try_run_sampling_request( response_id: _, token_usage, } => { + if let Some(state) = plan_mode_state.as_mut() { + flush_proposed_plan_segments_all(&sess, &turn_context, state).await; + } sess.update_token_usage_info(&turn_context, token_usage.as_ref()) .await; should_emit_turn_diff = true; @@ -3940,14 +4362,25 @@ async fn try_run_sampling_request( // In review child threads, suppress assistant text deltas; the // UI will show a selection popup from the final ReviewOutput. if let Some(active) = active_item.as_ref() { - let event = AgentMessageContentDeltaEvent { - thread_id: sess.conversation_id.to_string(), - turn_id: turn_context.sub_id.clone(), - item_id: active.id(), - delta: delta.clone(), - }; - sess.send_event(&turn_context, EventMsg::AgentMessageContentDelta(event)) - .await; + let item_id = active.id(); + if let Some(state) = plan_mode_state.as_mut() + && matches!(active, TurnItem::AgentMessage(_)) + { + let segments = state + .plan_parsers + .assistant_parser_mut(&item_id) + .parse(&delta); + handle_plan_segments(&sess, &turn_context, state, &item_id, segments).await; + } else { + let event = AgentMessageContentDeltaEvent { + thread_id: sess.conversation_id.to_string(), + turn_id: turn_context.sub_id.clone(), + item_id, + delta, + }; + sess.send_event(&turn_context, EventMsg::AgentMessageContentDelta(event)) + .await; + } } else { error_or_panic("OutputTextDelta without active item".to_string()); } diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index b233618eea..af9aacc769 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -61,6 +61,7 @@ pub(crate) async fn run_compact_task( ) { let start_event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), + collaboration_mode_kind: turn_context.collaboration_mode_kind, }); sess.send_event(&turn_context, start_event).await; run_compact_task_inner(sess.clone(), turn_context, input).await; diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index 35c157acfc..b45a2769dc 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -22,6 +22,7 @@ pub(crate) async fn run_inline_remote_auto_compact_task( pub(crate) async fn run_remote_compact_task(sess: Arc, turn_context: Arc) { let start_event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), + collaboration_mode_kind: turn_context.collaboration_mode_kind, }); sess.send_event(&turn_context, start_event).await; diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 63bba76507..ba47838e1f 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -49,9 +49,11 @@ mod model_provider_info; pub mod parse_command; pub mod path_utils; pub mod powershell; +mod proposed_plan_parser; pub mod sandboxing; mod session_prefix; mod stream_events_utils; +mod tagged_block_parser; mod text_encoding; pub mod token_data; mod truncate; diff --git a/codex-rs/core/src/proposed_plan_parser.rs b/codex-rs/core/src/proposed_plan_parser.rs new file mode 100644 index 0000000000..44be264f29 --- /dev/null +++ b/codex-rs/core/src/proposed_plan_parser.rs @@ -0,0 +1,185 @@ +use crate::tagged_block_parser::TagSpec; +use crate::tagged_block_parser::TaggedLineParser; +use crate::tagged_block_parser::TaggedLineSegment; + +const OPEN_TAG: &str = ""; +const CLOSE_TAG: &str = ""; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PlanTag { + ProposedPlan, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum ProposedPlanSegment { + Normal(String), + ProposedPlanStart, + ProposedPlanDelta(String), + ProposedPlanEnd, +} + +/// Parser for `` blocks emitted in plan mode. +/// +/// This is a thin wrapper around the generic line-based tag parser. It maps +/// tag-aware segments into plan-specific segments for downstream consumers. +#[derive(Debug)] +pub(crate) struct ProposedPlanParser { + parser: TaggedLineParser, +} + +impl ProposedPlanParser { + pub(crate) fn new() -> Self { + Self { + parser: TaggedLineParser::new(vec![TagSpec { + open: OPEN_TAG, + close: CLOSE_TAG, + tag: PlanTag::ProposedPlan, + }]), + } + } + + pub(crate) fn parse(&mut self, delta: &str) -> Vec { + self.parser + .parse(delta) + .into_iter() + .map(map_plan_segment) + .collect() + } + + pub(crate) fn finish(&mut self) -> Vec { + self.parser + .finish() + .into_iter() + .map(map_plan_segment) + .collect() + } +} + +fn map_plan_segment(segment: TaggedLineSegment) -> ProposedPlanSegment { + match segment { + TaggedLineSegment::Normal(text) => ProposedPlanSegment::Normal(text), + TaggedLineSegment::TagStart(PlanTag::ProposedPlan) => { + ProposedPlanSegment::ProposedPlanStart + } + TaggedLineSegment::TagDelta(PlanTag::ProposedPlan, text) => { + ProposedPlanSegment::ProposedPlanDelta(text) + } + TaggedLineSegment::TagEnd(PlanTag::ProposedPlan) => ProposedPlanSegment::ProposedPlanEnd, + } +} + +pub(crate) fn strip_proposed_plan_blocks(text: &str) -> String { + let mut parser = ProposedPlanParser::new(); + let mut out = String::new(); + for segment in parser.parse(text).into_iter().chain(parser.finish()) { + if let ProposedPlanSegment::Normal(delta) = segment { + out.push_str(&delta); + } + } + out +} + +pub(crate) fn extract_proposed_plan_text(text: &str) -> Option { + let mut parser = ProposedPlanParser::new(); + let mut plan_text = String::new(); + let mut saw_plan_block = false; + for segment in parser.parse(text).into_iter().chain(parser.finish()) { + match segment { + ProposedPlanSegment::ProposedPlanStart => { + saw_plan_block = true; + plan_text.clear(); + } + ProposedPlanSegment::ProposedPlanDelta(delta) => { + plan_text.push_str(&delta); + } + ProposedPlanSegment::ProposedPlanEnd | ProposedPlanSegment::Normal(_) => {} + } + } + saw_plan_block.then_some(plan_text) +} + +#[cfg(test)] +mod tests { + use super::ProposedPlanParser; + use super::ProposedPlanSegment; + use super::strip_proposed_plan_blocks; + use pretty_assertions::assert_eq; + + #[test] + fn streams_proposed_plan_segments() { + let mut parser = ProposedPlanParser::new(); + let mut segments = Vec::new(); + + for chunk in [ + "Intro text\n\n- step 1\n", + "\nOutro", + ] { + segments.extend(parser.parse(chunk)); + } + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![ + ProposedPlanSegment::Normal("Intro text\n".to_string()), + ProposedPlanSegment::ProposedPlanStart, + ProposedPlanSegment::ProposedPlanDelta("- step 1\n".to_string()), + ProposedPlanSegment::ProposedPlanEnd, + ProposedPlanSegment::Normal("Outro".to_string()), + ] + ); + } + + #[test] + fn preserves_non_tag_lines() { + let mut parser = ProposedPlanParser::new(); + let mut segments = parser.parse(" extra\n"); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![ProposedPlanSegment::Normal( + " extra\n".to_string() + )] + ); + } + + #[test] + fn closes_unterminated_plan_block_on_finish() { + let mut parser = ProposedPlanParser::new(); + let mut segments = parser.parse("\n- step 1\n"); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![ + ProposedPlanSegment::ProposedPlanStart, + ProposedPlanSegment::ProposedPlanDelta("- step 1\n".to_string()), + ProposedPlanSegment::ProposedPlanEnd, + ] + ); + } + + #[test] + fn closes_tag_line_without_trailing_newline() { + let mut parser = ProposedPlanParser::new(); + let mut segments = parser.parse("\n- step 1\n"); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![ + ProposedPlanSegment::ProposedPlanStart, + ProposedPlanSegment::ProposedPlanDelta("- step 1\n".to_string()), + ProposedPlanSegment::ProposedPlanEnd, + ] + ); + } + + #[test] + fn strips_proposed_plan_blocks_from_text() { + let text = "before\n\n- step\n\nafter"; + assert_eq!(strip_proposed_plan_blocks(text), "before\nafter"); + } +} diff --git a/codex-rs/core/src/rollout/policy.rs b/codex-rs/core/src/rollout/policy.rs index 6de60ae05c..5796bd1961 100644 --- a/codex-rs/core/src/rollout/policy.rs +++ b/codex-rs/core/src/rollout/policy.rs @@ -48,6 +48,12 @@ pub(crate) fn should_persist_event_msg(ev: &EventMsg) -> bool { | EventMsg::ThreadRolledBack(_) | EventMsg::UndoCompleted(_) | EventMsg::TurnAborted(_) => true, + EventMsg::ItemCompleted(event) => { + // Plan items are derived from streaming tags and are not part of the + // raw ResponseItem history, so we persist their completion to replay + // them on resume without bloating rollouts with every item lifecycle. + matches!(event.item, codex_protocol::items::TurnItem::Plan(_)) + } EventMsg::Error(_) | EventMsg::Warning(_) | EventMsg::TurnStarted(_) @@ -89,8 +95,8 @@ pub(crate) fn should_persist_event_msg(ev: &EventMsg) -> bool { | EventMsg::ViewImageToolCall(_) | EventMsg::DeprecationNotice(_) | EventMsg::ItemStarted(_) - | EventMsg::ItemCompleted(_) | EventMsg::AgentMessageContentDelta(_) + | EventMsg::PlanDelta(_) | EventMsg::ReasoningContentDelta(_) | EventMsg::ReasoningRawContentDelta(_) | EventMsg::SkillsUpdateAvailable diff --git a/codex-rs/core/src/stream_events_utils.rs b/codex-rs/core/src/stream_events_utils.rs index 930ce5afd6..91f7efdb01 100644 --- a/codex-rs/core/src/stream_events_utils.rs +++ b/codex-rs/core/src/stream_events_utils.rs @@ -1,6 +1,7 @@ use std::pin::Pin; use std::sync::Arc; +use codex_protocol::config_types::ModeKind; use codex_protocol::items::TurnItem; use tokio_util::sync::CancellationToken; @@ -10,6 +11,7 @@ use crate::error::CodexErr; use crate::error::Result; use crate::function_tool::FunctionCallError; use crate::parse_turn_item; +use crate::proposed_plan_parser::strip_proposed_plan_blocks; use crate::tools::parallel::ToolCallRuntime; use crate::tools::router::ToolRouter; use codex_protocol::models::FunctionCallOutputPayload; @@ -46,6 +48,7 @@ pub(crate) async fn handle_output_item_done( previously_active_item: Option, ) -> Result { let mut output = OutputItemResult::default(); + let plan_mode = ctx.turn_context.collaboration_mode_kind == ModeKind::Plan; match ToolRouter::build_tool_call(ctx.sess.as_ref(), item.clone()).await { // The model emitted a tool call; log it, persist the item immediately, and queue the tool execution. @@ -74,7 +77,7 @@ pub(crate) async fn handle_output_item_done( } // No tool call: convert messages/reasoning into turn items and mark them as complete. Ok(None) => { - if let Some(turn_item) = handle_non_tool_response_item(&item).await { + if let Some(turn_item) = handle_non_tool_response_item(&item, plan_mode).await { if previously_active_item.is_none() { ctx.sess .emit_turn_item_started(&ctx.turn_context, &turn_item) @@ -89,7 +92,7 @@ pub(crate) async fn handle_output_item_done( ctx.sess .record_conversation_items(&ctx.turn_context, std::slice::from_ref(&item)) .await; - let last_agent_message = last_assistant_message_from_item(&item); + let last_agent_message = last_assistant_message_from_item(&item, plan_mode); output.last_agent_message = last_agent_message; } @@ -155,13 +158,31 @@ pub(crate) async fn handle_output_item_done( Ok(output) } -pub(crate) async fn handle_non_tool_response_item(item: &ResponseItem) -> Option { +pub(crate) async fn handle_non_tool_response_item( + item: &ResponseItem, + plan_mode: bool, +) -> Option { debug!(?item, "Output item"); match item { ResponseItem::Message { .. } | ResponseItem::Reasoning { .. } - | ResponseItem::WebSearchCall { .. } => parse_turn_item(item), + | ResponseItem::WebSearchCall { .. } => { + let mut turn_item = parse_turn_item(item)?; + if plan_mode && let TurnItem::AgentMessage(agent_message) = &mut turn_item { + let combined = agent_message + .content + .iter() + .map(|entry| match entry { + codex_protocol::items::AgentMessageContent::Text { text } => text.as_str(), + }) + .collect::(); + let stripped = strip_proposed_plan_blocks(&combined); + agent_message.content = + vec![codex_protocol::items::AgentMessageContent::Text { text: stripped }]; + } + Some(turn_item) + } ResponseItem::FunctionCallOutput { .. } | ResponseItem::CustomToolCallOutput { .. } => { debug!("unexpected tool output from stream"); None @@ -170,14 +191,29 @@ pub(crate) async fn handle_non_tool_response_item(item: &ResponseItem) -> Option } } -pub(crate) fn last_assistant_message_from_item(item: &ResponseItem) -> Option { +pub(crate) fn last_assistant_message_from_item( + item: &ResponseItem, + plan_mode: bool, +) -> Option { if let ResponseItem::Message { role, content, .. } = item && role == "assistant" { - return content.iter().rev().find_map(|ci| match ci { - codex_protocol::models::ContentItem::OutputText { text } => Some(text.clone()), - _ => None, - }); + let combined = content + .iter() + .filter_map(|ci| match ci { + codex_protocol::models::ContentItem::OutputText { text } => Some(text.as_str()), + _ => None, + }) + .collect::(); + if combined.is_empty() { + return None; + } + return if plan_mode { + let stripped = strip_proposed_plan_blocks(&combined); + (!stripped.trim().is_empty()).then_some(stripped) + } else { + Some(combined) + }; } None } diff --git a/codex-rs/core/src/tagged_block_parser.rs b/codex-rs/core/src/tagged_block_parser.rs new file mode 100644 index 0000000000..46ec012c30 --- /dev/null +++ b/codex-rs/core/src/tagged_block_parser.rs @@ -0,0 +1,314 @@ +//! Line-based tag block parsing for streamed text. +//! +//! The parser buffers each line until it can disprove that the line is a tag, +//! which is required for tags that must appear alone on a line. For example, +//! Proposed Plan output uses `` and `` tags +//! on their own lines so clients can stream plan content separately. + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct TagSpec { + pub(crate) open: &'static str, + pub(crate) close: &'static str, + pub(crate) tag: T, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum TaggedLineSegment { + Normal(String), + TagStart(T), + TagDelta(T, String), + TagEnd(T), +} + +/// Stateful line parser that splits input into normal text vs tag blocks. +/// +/// How it works: +/// - While reading a line, we buffer characters until the line either finishes +/// (`\n`) or stops matching any tag prefix (after `trim_start`). +/// - If it stops matching a tag prefix, the buffered line is immediately +/// emitted as text and we continue in "plain text" mode until the next +/// newline. +/// - When a full line is available, we compare it to the open/close tags; tag +/// lines emit TagStart/TagEnd, otherwise the line is emitted as text. +/// - `finish()` flushes any buffered line and auto-closes an unterminated tag, +/// which keeps streaming resilient to missing closing tags. +#[derive(Debug, Default)] +pub(crate) struct TaggedLineParser +where + T: Copy + Eq, +{ + specs: Vec>, + active_tag: Option, + detect_tag: bool, + line_buffer: String, +} + +impl TaggedLineParser +where + T: Copy + Eq, +{ + pub(crate) fn new(specs: Vec>) -> Self { + Self { + specs, + active_tag: None, + detect_tag: true, + line_buffer: String::new(), + } + } + + /// Parse a streamed delta into line-aware segments. + pub(crate) fn parse(&mut self, delta: &str) -> Vec> { + let mut segments = Vec::new(); + let mut run = String::new(); + + for ch in delta.chars() { + if self.detect_tag { + if !run.is_empty() { + self.push_text(std::mem::take(&mut run), &mut segments); + } + self.line_buffer.push(ch); + if ch == '\n' { + self.finish_line(&mut segments); + continue; + } + let slug = self.line_buffer.trim_start(); + if slug.is_empty() || self.is_tag_prefix(slug) { + continue; + } + // This line cannot be a tag line, so flush it immediately. + let buffered = std::mem::take(&mut self.line_buffer); + self.detect_tag = false; + self.push_text(buffered, &mut segments); + continue; + } + + run.push(ch); + if ch == '\n' { + self.push_text(std::mem::take(&mut run), &mut segments); + self.detect_tag = true; + } + } + + if !run.is_empty() { + self.push_text(run, &mut segments); + } + + segments + } + + /// Flush any buffered text and close an unterminated tag block. + pub(crate) fn finish(&mut self) -> Vec> { + let mut segments = Vec::new(); + if !self.line_buffer.is_empty() { + let buffered = std::mem::take(&mut self.line_buffer); + let without_newline = buffered.strip_suffix('\n').unwrap_or(&buffered); + let slug = without_newline.trim_start().trim_end(); + + if let Some(tag) = self.match_open(slug) + && self.active_tag.is_none() + { + push_segment(&mut segments, TaggedLineSegment::TagStart(tag)); + self.active_tag = Some(tag); + } else if let Some(tag) = self.match_close(slug) + && self.active_tag == Some(tag) + { + push_segment(&mut segments, TaggedLineSegment::TagEnd(tag)); + self.active_tag = None; + } else { + // The buffered line never proved to be a tag line. + self.push_text(buffered, &mut segments); + } + } + if let Some(tag) = self.active_tag.take() { + push_segment(&mut segments, TaggedLineSegment::TagEnd(tag)); + } + self.detect_tag = true; + segments + } + + fn finish_line(&mut self, segments: &mut Vec>) { + let line = std::mem::take(&mut self.line_buffer); + let without_newline = line.strip_suffix('\n').unwrap_or(&line); + let slug = without_newline.trim_start().trim_end(); + + if let Some(tag) = self.match_open(slug) + && self.active_tag.is_none() + { + push_segment(segments, TaggedLineSegment::TagStart(tag)); + self.active_tag = Some(tag); + self.detect_tag = true; + return; + } + + if let Some(tag) = self.match_close(slug) + && self.active_tag == Some(tag) + { + push_segment(segments, TaggedLineSegment::TagEnd(tag)); + self.active_tag = None; + self.detect_tag = true; + return; + } + + self.detect_tag = true; + self.push_text(line, segments); + } + + fn push_text(&self, text: String, segments: &mut Vec>) { + if let Some(tag) = self.active_tag { + push_segment(segments, TaggedLineSegment::TagDelta(tag, text)); + } else { + push_segment(segments, TaggedLineSegment::Normal(text)); + } + } + + fn is_tag_prefix(&self, slug: &str) -> bool { + let slug = slug.trim_end(); + self.specs + .iter() + .any(|spec| spec.open.starts_with(slug) || spec.close.starts_with(slug)) + } + + fn match_open(&self, slug: &str) -> Option { + self.specs + .iter() + .find(|spec| spec.open == slug) + .map(|spec| spec.tag) + } + + fn match_close(&self, slug: &str) -> Option { + self.specs + .iter() + .find(|spec| spec.close == slug) + .map(|spec| spec.tag) + } +} + +fn push_segment(segments: &mut Vec>, segment: TaggedLineSegment) +where + T: Copy + Eq, +{ + match segment { + TaggedLineSegment::Normal(delta) => { + if delta.is_empty() { + return; + } + if let Some(TaggedLineSegment::Normal(existing)) = segments.last_mut() { + existing.push_str(&delta); + return; + } + segments.push(TaggedLineSegment::Normal(delta)); + } + TaggedLineSegment::TagDelta(tag, delta) => { + if delta.is_empty() { + return; + } + if let Some(TaggedLineSegment::TagDelta(existing_tag, existing)) = segments.last_mut() + && *existing_tag == tag + { + existing.push_str(&delta); + return; + } + segments.push(TaggedLineSegment::TagDelta(tag, delta)); + } + TaggedLineSegment::TagStart(tag) => { + segments.push(TaggedLineSegment::TagStart(tag)); + } + TaggedLineSegment::TagEnd(tag) => { + segments.push(TaggedLineSegment::TagEnd(tag)); + } + } +} + +#[cfg(test)] +mod tests { + use super::TagSpec; + use super::TaggedLineParser; + use super::TaggedLineSegment; + use pretty_assertions::assert_eq; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Tag { + Block, + } + + fn parser() -> TaggedLineParser { + TaggedLineParser::new(vec![TagSpec { + open: "", + close: "", + tag: Tag::Block, + }]) + } + + #[test] + fn buffers_prefix_until_tag_is_decided() { + let mut parser = parser(); + let mut segments = parser.parse("\nline\n\n")); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![ + TaggedLineSegment::TagStart(Tag::Block), + TaggedLineSegment::TagDelta(Tag::Block, "line\n".to_string()), + TaggedLineSegment::TagEnd(Tag::Block), + ] + ); + } + + #[test] + fn rejects_tag_lines_with_extra_text() { + let mut parser = parser(); + let mut segments = parser.parse(" extra\n"); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![TaggedLineSegment::Normal(" extra\n".to_string())] + ); + } + + #[test] + fn closes_unterminated_tag_on_finish() { + let mut parser = parser(); + let mut segments = parser.parse("\nline\n"); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![ + TaggedLineSegment::TagStart(Tag::Block), + TaggedLineSegment::TagDelta(Tag::Block, "line\n".to_string()), + TaggedLineSegment::TagEnd(Tag::Block), + ] + ); + } + + #[test] + fn accepts_tags_with_trailing_whitespace() { + let mut parser = parser(); + let mut segments = parser.parse(" \nline\n \n"); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![ + TaggedLineSegment::TagStart(Tag::Block), + TaggedLineSegment::TagDelta(Tag::Block, "line\n".to_string()), + TaggedLineSegment::TagEnd(Tag::Block), + ] + ); + } + + #[test] + fn passes_through_plain_text() { + let mut parser = parser(); + let mut segments = parser.parse("plain text\n"); + segments.extend(parser.finish()); + + assert_eq!( + segments, + vec![TaggedLineSegment::Normal("plain text\n".to_string())] + ); + } +} diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index 4305417a35..177d122554 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -67,6 +67,7 @@ impl SessionTask for UserShellCommandTask { let event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), + collaboration_mode_kind: turn_context.collaboration_mode_kind, }); let session = session.clone_session(); session.send_event(turn_context.as_ref(), event).await; diff --git a/codex-rs/core/src/tools/handlers/plan.rs b/codex-rs/core/src/tools/handlers/plan.rs index 073319bf1c..79e332a77e 100644 --- a/codex-rs/core/src/tools/handlers/plan.rs +++ b/codex-rs/core/src/tools/handlers/plan.rs @@ -10,6 +10,7 @@ use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; use crate::tools::spec::JsonSchema; use async_trait::async_trait; +use codex_protocol::config_types::ModeKind; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::EventMsg; use std::collections::BTreeMap; @@ -103,6 +104,11 @@ pub(crate) async fn handle_update_plan( arguments: String, _call_id: String, ) -> Result { + if turn_context.collaboration_mode_kind == ModeKind::Plan { + return Err(FunctionCallError::RespondToModel( + "update_plan is a TODO/checklist tool and is not allowed in Plan mode".to_string(), + )); + } let args = parse_update_plan_arguments(&arguments)?; session .send_event(turn_context, EventMsg::PlanUpdate(args)) diff --git a/codex-rs/core/templates/collaboration_mode/plan.md b/codex-rs/core/templates/collaboration_mode/plan.md index cffbf16d36..395b6c368b 100644 --- a/codex-rs/core/templates/collaboration_mode/plan.md +++ b/codex-rs/core/templates/collaboration_mode/plan.md @@ -8,6 +8,12 @@ You are in **Plan Mode** until a developer message explicitly ends it. Plan Mode is not changed by user intent, tone, or imperative language. If a user asks for execution while still in Plan Mode, treat it as a request to **plan the execution**, not perform it. +## Plan Mode vs update_plan tool + +Plan Mode is a collaboration mode that can involve requesting user input and eventually issuing a `` block. + +Separately, `update_plan` is a checklist/progress/TODOs tool; it does not enter or exit Plan Mode. Do not confuse it with Plan mode or try to use it while in Plan mode. If you try to use `update_plan` in Plan mode, it will return an error. + ## Execution vs. mutation in Plan Mode You may explore and execute **non-mutating** actions that improve the plan. You must not perform **mutating** actions. @@ -96,6 +102,22 @@ Use the `request_user_input` tool only for decisions that materially change the Only output the final plan when it is decision complete and leaves no decisions to the implementer. +When you present the official plan, wrap it in a `` block so the client can render it specially: + +1) The opening tag must be on its own line. +2) Start the plan content on the next line (no text on the same line as the tag). +3) The closing tag must be on its own line. +4) Use Markdown inside the block. +5) Keep the tags exactly as `` and `` (do not translate or rename them), even if the plan content is in another language. + +Example: + + +# Plan title +- Step 1 +- Step 2 + + The final plan must be plan-only and include: * A clear title @@ -106,6 +128,6 @@ The final plan must be plan-only and include: * Test cases * Explicit assumptions and defaults chosen where needed -Do not ask "should I proceed?" in the final output. +Do not ask "should I proceed?" in the final output. The user can easily switch out of Plan mode and request implementation if you have included a `` block in your response. Alternatively, they can decide to stay in Plan mode and continue refining the plan. -Only produce the final answer when you are presenting the complete spec. +Only produce at most one `` block per turn, and only when you are presenting a complete spec. diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index 08ac598388..7ef73dce94 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -5,6 +5,10 @@ use codex_core::protocol::EventMsg; use codex_core::protocol::ItemCompletedEvent; use codex_core::protocol::ItemStartedEvent; use codex_core::protocol::Op; +use codex_protocol::config_types::CollaborationMode; +use codex_protocol::config_types::ModeKind; +use codex_protocol::config_types::Settings; +use codex_protocol::items::AgentMessageContent; use codex_protocol::items::TurnItem; use codex_protocol::models::WebSearchAction; use codex_protocol::user_input::ByteRange; @@ -27,6 +31,7 @@ use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; +use core_test_support::wait_for_event; use core_test_support::wait_for_event_match; use pretty_assertions::assert_eq; @@ -327,6 +332,268 @@ async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn plan_mode_emits_plan_item_from_proposed_plan_block() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + + let TestCodex { + codex, + session_configured, + .. + } = test_codex().build(&server).await?; + + let plan_block = "\n- Step 1\n- Step 2\n\n"; + let full_message = format!("Intro\n{plan_block}Outro"); + let stream = sse(vec![ + ev_response_created("resp-1"), + ev_message_item_added("msg-1", ""), + ev_output_text_delta(&full_message), + ev_assistant_message("msg-1", &full_message), + ev_completed("resp-1"), + ]); + mount_sse_once(&server, stream).await; + + let collaboration_mode = CollaborationMode { + mode: ModeKind::Plan, + settings: Settings { + model: session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }; + + codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "please plan".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: std::env::current_dir()?, + approval_policy: codex_core::protocol::AskForApproval::Never, + sandbox_policy: codex_core::protocol::SandboxPolicy::DangerFullAccess, + model: session_configured.model.clone(), + effort: None, + summary: codex_protocol::config_types::ReasoningSummary::Auto, + collaboration_mode: Some(collaboration_mode), + personality: None, + }) + .await?; + + let plan_delta = wait_for_event_match(&codex, |ev| match ev { + EventMsg::PlanDelta(event) => Some(event.clone()), + _ => None, + }) + .await; + + let plan_completed = wait_for_event_match(&codex, |ev| match ev { + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::Plan(item), + .. + }) => Some(item.clone()), + _ => None, + }) + .await; + + assert_eq!( + plan_delta.thread_id, + session_configured.session_id.to_string() + ); + assert_eq!(plan_delta.delta, "- Step 1\n- Step 2\n"); + assert_eq!(plan_completed.text, "- Step 1\n- Step 2\n"); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn plan_mode_strips_plan_from_agent_messages() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + + let TestCodex { + codex, + session_configured, + .. + } = test_codex().build(&server).await?; + + let plan_block = "\n- Step 1\n- Step 2\n\n"; + let full_message = format!("Intro\n{plan_block}Outro"); + let stream = sse(vec![ + ev_response_created("resp-1"), + ev_message_item_added("msg-1", ""), + ev_output_text_delta(&full_message), + ev_assistant_message("msg-1", &full_message), + ev_completed("resp-1"), + ]); + mount_sse_once(&server, stream).await; + + let collaboration_mode = CollaborationMode { + mode: ModeKind::Plan, + settings: Settings { + model: session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }; + + codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "please plan".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: std::env::current_dir()?, + approval_policy: codex_core::protocol::AskForApproval::Never, + sandbox_policy: codex_core::protocol::SandboxPolicy::DangerFullAccess, + model: session_configured.model.clone(), + effort: None, + summary: codex_protocol::config_types::ReasoningSummary::Auto, + collaboration_mode: Some(collaboration_mode), + personality: None, + }) + .await?; + + let mut agent_deltas = Vec::new(); + let mut plan_delta = None; + let mut agent_item = None; + let mut plan_item = None; + + while plan_delta.is_none() || agent_item.is_none() || plan_item.is_none() { + let ev = wait_for_event(&codex, |_| true).await; + match ev { + EventMsg::AgentMessageContentDelta(event) => { + agent_deltas.push(event.delta); + } + EventMsg::PlanDelta(event) => { + plan_delta = Some(event.delta); + } + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::AgentMessage(item), + .. + }) => { + agent_item = Some(item); + } + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::Plan(item), + .. + }) => { + plan_item = Some(item); + } + _ => {} + } + } + + let agent_text = agent_deltas.concat(); + assert_eq!(agent_text, "Intro\nOutro"); + assert_eq!(plan_delta.unwrap(), "- Step 1\n- Step 2\n"); + assert_eq!(plan_item.unwrap().text, "- Step 1\n- Step 2\n"); + let agent_text_from_item: String = agent_item + .unwrap() + .content + .iter() + .map(|entry| match entry { + AgentMessageContent::Text { text } => text.as_str(), + }) + .collect(); + assert_eq!(agent_text_from_item, "Intro\nOutro"); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn plan_mode_handles_missing_plan_close_tag() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + + let TestCodex { + codex, + session_configured, + .. + } = test_codex().build(&server).await?; + + let full_message = "Intro\n\n- Step 1\n"; + let stream = sse(vec![ + ev_response_created("resp-1"), + ev_message_item_added("msg-1", ""), + ev_output_text_delta(full_message), + ev_assistant_message("msg-1", full_message), + ev_completed("resp-1"), + ]); + mount_sse_once(&server, stream).await; + + let collaboration_mode = CollaborationMode { + mode: ModeKind::Plan, + settings: Settings { + model: session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }; + + codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "please plan".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: std::env::current_dir()?, + approval_policy: codex_core::protocol::AskForApproval::Never, + sandbox_policy: codex_core::protocol::SandboxPolicy::DangerFullAccess, + model: session_configured.model.clone(), + effort: None, + summary: codex_protocol::config_types::ReasoningSummary::Auto, + collaboration_mode: Some(collaboration_mode), + personality: None, + }) + .await?; + + let mut plan_delta = None; + let mut plan_item = None; + let mut agent_item = None; + + while plan_delta.is_none() || plan_item.is_none() || agent_item.is_none() { + let ev = wait_for_event(&codex, |_| true).await; + match ev { + EventMsg::PlanDelta(event) => { + plan_delta = Some(event.delta); + } + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::Plan(item), + .. + }) => { + plan_item = Some(item); + } + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::AgentMessage(item), + .. + }) => { + agent_item = Some(item); + } + _ => {} + } + } + + assert_eq!(plan_delta.unwrap(), "- Step 1\n"); + assert_eq!(plan_item.unwrap().text, "- Step 1\n"); + let agent_text_from_item: String = agent_item + .unwrap() + .content + .iter() + .map(|entry| match entry { + AgentMessageContent::Text { text } => text.as_str(), + }) + .collect(); + assert_eq!(agent_text_from_item, "Intro\n"); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); diff --git a/codex-rs/debug-client/src/reader.rs b/codex-rs/debug-client/src/reader.rs index 92161638ff..48841f699d 100644 --- a/codex-rs/debug-client/src/reader.rs +++ b/codex-rs/debug-client/src/reader.rs @@ -229,6 +229,11 @@ fn emit_filtered_item(item: ThreadItem, thread_id: &str, output: &Output) -> any let label = output.format_label("assistant", LabelColor::Assistant); output.server_line(&format!("{thread_label} {label}: {text}"))?; } + ThreadItem::Plan { text, .. } => { + let label = output.format_label("assistant", LabelColor::Assistant); + output.server_line(&format!("{thread_label} {label}: plan"))?; + write_multiline(output, &thread_label, &format!("{label}:"), &text)?; + } ThreadItem::CommandExecution { command, status, diff --git a/codex-rs/docs/protocol_v1.md b/codex-rs/docs/protocol_v1.md index 74b5546012..8f68ecc9a6 100644 --- a/codex-rs/docs/protocol_v1.md +++ b/codex-rs/docs/protocol_v1.md @@ -74,8 +74,11 @@ For complete documentation of the `Op` and `EventMsg` variants, refer to [protoc - `Op::UserTurn` and `Op::OverrideTurnContext` accept an optional `personality` override that updates the model’s communication style - `EventMsg` - `EventMsg::AgentMessage` – Messages from the `Model` + - `EventMsg::AgentMessageContentDelta` – Streaming assistant text + - `EventMsg::PlanDelta` – Streaming proposed plan text when the model emits a `` block in plan mode - `EventMsg::ExecApprovalRequest` – Request approval from user to execute a command - - `EventMsg::RequestUserInput` – Request user input for a tool call (questions must include options; the client always adds a free-form choice) + - `EventMsg::RequestUserInput` – Request user input for a tool call (questions can include options plus `isOther` to add a free-form choice) + - `EventMsg::TurnStarted` – Turn start metadata including `model_context_window` and `collaboration_mode_kind` - `EventMsg::TurnComplete` – A turn completed successfully - `EventMsg::Error` – A turn stopped with an error - `EventMsg::Warning` – A non-fatal warning that the client should surface to the user diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index e11e1fcadb..8469dc42ed 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -20,6 +20,7 @@ use codex_core::protocol::EventMsg; use codex_core::protocol::ExecCommandBeginEvent; use codex_core::protocol::ExecCommandEndEvent; use codex_core::protocol::FileChange; +use codex_core::protocol::ItemCompletedEvent; use codex_core::protocol::McpInvocation; use codex_core::protocol::McpToolCallBeginEvent; use codex_core::protocol::McpToolCallEndEvent; @@ -33,6 +34,7 @@ use codex_core::protocol::TurnDiffEvent; use codex_core::protocol::WarningEvent; use codex_core::protocol::WebSearchEndEvent; use codex_core::web_search::web_search_detail; +use codex_protocol::items::TurnItem; use codex_protocol::num_format::format_with_separators; use owo_colors::OwoColorize; use owo_colors::Style; @@ -73,6 +75,7 @@ pub(crate) struct EventProcessorWithHumanOutput { last_message_path: Option, last_total_token_usage: Option, final_message: Option, + last_proposed_plan: Option, } impl EventProcessorWithHumanOutput { @@ -99,6 +102,7 @@ impl EventProcessorWithHumanOutput { last_message_path, last_total_token_usage: None, final_message: None, + last_proposed_plan: None, } } else { Self { @@ -116,6 +120,7 @@ impl EventProcessorWithHumanOutput { last_message_path, last_total_token_usage: None, final_message: None, + last_proposed_plan: None, } } } @@ -260,12 +265,14 @@ impl EventProcessor for EventProcessorWithHumanOutput { ); } EventMsg::TurnComplete(TurnCompleteEvent { last_agent_message }) => { - let last_message = last_agent_message.as_deref(); + let last_message = last_agent_message + .as_deref() + .or(self.last_proposed_plan.as_deref()); if let Some(output_file) = self.last_message_path.as_deref() { handle_last_message(last_message, output_file); } - self.final_message = last_agent_message; + self.final_message = last_agent_message.or_else(|| self.last_proposed_plan.clone()); return CodexStatus::InitiateShutdown; } @@ -297,6 +304,12 @@ impl EventProcessor for EventProcessorWithHumanOutput { message, ); } + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::Plan(item), + .. + }) => { + self.last_proposed_plan = Some(item.text); + } EventMsg::ExecCommandBegin(ExecCommandBeginEvent { command, cwd, .. }) => { eprint!( "{}\n{} in {}", @@ -769,6 +782,7 @@ impl EventProcessor for EventProcessorWithHumanOutput { | EventMsg::ItemStarted(_) | EventMsg::ItemCompleted(_) | EventMsg::AgentMessageContentDelta(_) + | EventMsg::PlanDelta(_) | EventMsg::ReasoningContentDelta(_) | EventMsg::ReasoningRawContentDelta(_) | EventMsg::SkillsUpdateAvailable diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output.rs b/codex-rs/exec/src/event_processor_with_jsonl_output.rs index 687df574d6..823fa7b810 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output.rs @@ -58,6 +58,7 @@ use tracing::warn; pub struct EventProcessorWithJsonOutput { last_message_path: Option, + last_proposed_plan: Option, next_event_id: AtomicU64, // Tracks running commands by call_id, including the associated item id. running_commands: HashMap, @@ -102,6 +103,7 @@ impl EventProcessorWithJsonOutput { pub fn new(last_message_path: Option) -> Self { Self { last_message_path, + last_proposed_plan: None, next_event_id: AtomicU64::new(0), running_commands: HashMap::new(), running_patch_applies: HashMap::new(), @@ -119,6 +121,13 @@ impl EventProcessorWithJsonOutput { protocol::EventMsg::SessionConfigured(ev) => self.handle_session_configured(ev), protocol::EventMsg::ThreadNameUpdated(_) => Vec::new(), protocol::EventMsg::AgentMessage(ev) => self.handle_agent_message(ev), + protocol::EventMsg::ItemCompleted(protocol::ItemCompletedEvent { + item: codex_protocol::items::TurnItem::Plan(item), + .. + }) => { + self.last_proposed_plan = Some(item.text.clone()); + Vec::new() + } protocol::EventMsg::AgentReasoning(ev) => self.handle_reasoning_event(ev), protocol::EventMsg::ExecCommandBegin(ev) => self.handle_exec_command_begin(ev), protocol::EventMsg::ExecCommandEnd(ev) => self.handle_exec_command_end(ev), @@ -855,7 +864,10 @@ impl EventProcessor for EventProcessorWithJsonOutput { last_agent_message, }) => { if let Some(output_file) = self.last_message_path.as_deref() { - handle_last_message(last_agent_message.as_deref(), output_file); + let last_message = last_agent_message + .as_deref() + .or(self.last_proposed_plan.as_deref()); + handle_last_message(last_message, output_file); } CodexStatus::InitiateShutdown } diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index dc5f220fd3..e405195e7e 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -55,6 +55,7 @@ use codex_exec::exec_events::TurnStartedEvent; use codex_exec::exec_events::Usage; use codex_exec::exec_events::WebSearchItem; use codex_protocol::ThreadId; +use codex_protocol::config_types::ModeKind; use codex_protocol::models::WebSearchAction; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; @@ -117,6 +118,7 @@ fn task_started_produces_turn_started_event() { "t1", EventMsg::TurnStarted(codex_core::protocol::TurnStartedEvent { model_context_window: Some(32_000), + collaboration_mode_kind: ModeKind::Custom, }), )); diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index c96bc60706..1dbdbc767b 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -252,6 +252,9 @@ async fn run_codex_tool_session_inner( .await; continue; } + EventMsg::PlanDelta(_) => { + continue; + } EventMsg::Error(err_event) => { // Always respond in tools/call's expected shape, and include conversationId so the client can resume. let result = create_call_tool_result_with_thread_id( diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 86d39d6d4a..db2964c40c 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -166,10 +166,13 @@ pub enum AltScreenMode { } /// Initial collaboration mode to use when the TUI starts. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, TS)] +#[derive( + Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, TS, Default, +)] #[serde(rename_all = "snake_case")] pub enum ModeKind { Plan, + #[default] Code, PairProgramming, Execute, diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index e7c916a61d..9a387a9d22 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -20,6 +20,7 @@ use ts_rs::TS; pub enum TurnItem { UserMessage(UserMessageItem), AgentMessage(AgentMessageItem), + Plan(PlanItem), Reasoning(ReasoningItem), WebSearch(WebSearchItem), ContextCompaction(ContextCompactionItem), @@ -44,6 +45,12 @@ pub struct AgentMessageItem { pub content: Vec, } +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] +pub struct PlanItem { + pub id: String, + pub text: String, +} + #[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] pub struct ReasoningItem { pub id: String, @@ -218,6 +225,7 @@ impl TurnItem { match self { TurnItem::UserMessage(item) => item.id.clone(), TurnItem::AgentMessage(item) => item.id.clone(), + TurnItem::Plan(item) => item.id.clone(), TurnItem::Reasoning(item) => item.id.clone(), TurnItem::WebSearch(item) => item.id.clone(), TurnItem::ContextCompaction(item) => item.id.clone(), @@ -228,6 +236,7 @@ impl TurnItem { match self { TurnItem::UserMessage(item) => vec![item.as_legacy_event()], TurnItem::AgentMessage(item) => item.as_legacy_events(), + TurnItem::Plan(_) => Vec::new(), TurnItem::WebSearch(item) => vec![item.as_legacy_event()], TurnItem::Reasoning(item) => item.as_legacy_events(show_raw_agent_reasoning), TurnItem::ContextCompaction(item) => vec![item.as_legacy_event()], diff --git a/codex-rs/protocol/src/plan_tool.rs b/codex-rs/protocol/src/plan_tool.rs index a9038eb03b..affb4c1896 100644 --- a/codex-rs/protocol/src/plan_tool.rs +++ b/codex-rs/protocol/src/plan_tool.rs @@ -22,6 +22,7 @@ pub struct PlanItemArg { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, TS)] #[serde(deny_unknown_fields)] pub struct UpdatePlanArgs { + /// Arguments for the `update_plan` todo/checklist tool (not plan mode). #[serde(default)] pub explanation: Option, pub plan: Vec, diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index fbaf4f4457..929d7d73c7 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -14,6 +14,7 @@ use std::time::Duration; use crate::ThreadId; use crate::approvals::ElicitationRequestEvent; use crate::config_types::CollaborationMode; +use crate::config_types::ModeKind; use crate::config_types::Personality; use crate::config_types::ReasoningSummary as ReasoningSummaryConfig; use crate::config_types::WindowsSandboxLevel; @@ -838,6 +839,7 @@ pub enum EventMsg { ItemCompleted(ItemCompletedEvent), AgentMessageContentDelta(AgentMessageContentDeltaEvent), + PlanDelta(PlanDeltaEvent), ReasoningContentDelta(ReasoningContentDeltaEvent), ReasoningRawContentDelta(ReasoningRawContentDeltaEvent), @@ -1017,6 +1019,14 @@ impl HasLegacyEvent for AgentMessageContentDeltaEvent { } } +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] +pub struct PlanDeltaEvent { + pub thread_id: String, + pub turn_id: String, + pub item_id: String, + pub delta: String, +} + #[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] pub struct ReasoningContentDeltaEvent { pub thread_id: String, @@ -1107,6 +1117,8 @@ pub struct TurnCompleteEvent { pub struct TurnStartedEvent { // TODO(aibrahim): make this not optional pub model_context_window: Option, + #[serde(default)] + pub collaboration_mode_kind: ModeKind, } #[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, TS)] diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 491ea3add3..7793f0a39c 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -196,6 +196,7 @@ mod skills; use self::skills::collect_tool_mentions; use self::skills::find_app_mentions; use self::skills::find_skill_mentions_with_tool_mentions; +use crate::streaming::controller::PlanStreamController; use crate::streaming::controller::StreamController; use chrono::Local; @@ -485,6 +486,8 @@ pub(crate) struct ChatWidget { rate_limit_poller: Option>, // Stream lifecycle controller stream_controller: Option, + // Stream lifecycle controller for proposed plan output. + plan_stream_controller: Option, running_commands: HashMap, suppressed_exec_calls: HashSet, skills_all: Vec, @@ -553,6 +556,12 @@ pub(crate) struct ChatWidget { had_work_activity: bool, // Whether the current turn emitted a plan update. saw_plan_update_this_turn: bool, + // Whether the current turn emitted a proposed plan item. + saw_plan_item_this_turn: bool, + // Incremental buffer for streamed plan content. + plan_delta_buffer: String, + // True while a plan item is streaming. + plan_item_active: bool, // Status-indicator elapsed seconds captured at the last emitted final-message separator. // // This lets the separator show per-chunk work time (since the previous separator) rather than @@ -896,7 +905,7 @@ impl ChatWidget { fn on_agent_message(&mut self, message: String) { // If we have a stream_controller, then the final agent message is redundant and will be a // duplicate of what has already been streamed. - if self.stream_controller.is_none() { + if self.stream_controller.is_none() && !message.is_empty() { self.handle_streaming_delta(message); } self.flush_answer_stream_with_separator(); @@ -908,6 +917,56 @@ impl ChatWidget { self.handle_streaming_delta(delta); } + fn on_plan_delta(&mut self, delta: String) { + if self.active_mode_kind() != ModeKind::Plan { + return; + } + if !self.plan_item_active { + self.plan_item_active = true; + self.plan_delta_buffer.clear(); + } + self.plan_delta_buffer.push_str(&delta); + // Before streaming plan content, flush any active exec cell group. + self.flush_unified_exec_wait_streak(); + self.flush_active_cell(); + + if self.plan_stream_controller.is_none() { + self.plan_stream_controller = Some(PlanStreamController::new( + self.last_rendered_width.get().map(|w| w.saturating_sub(4)), + )); + } + if let Some(controller) = self.plan_stream_controller.as_mut() + && controller.push(&delta) + { + self.app_event_tx.send(AppEvent::StartCommitAnimation); + } + self.request_redraw(); + } + + fn on_plan_item_completed(&mut self, text: String) { + let streamed_plan = self.plan_delta_buffer.trim().to_string(); + let plan_text = if text.trim().is_empty() { + streamed_plan + } else { + text + }; + self.plan_delta_buffer.clear(); + self.plan_item_active = false; + self.saw_plan_item_this_turn = true; + if let Some(mut controller) = self.plan_stream_controller.take() + && let Some(cell) = controller.finalize() + { + self.add_boxed_history(cell); + // TODO: Replace streamed output with the final plan item text if plan streaming is + // removed or if we need to reconcile mismatches between streamed and final content. + return; + } + if plan_text.is_empty() { + return; + } + self.add_to_history(history_cell::new_proposed_plan(plan_text)); + } + fn on_agent_reasoning_delta(&mut self, delta: String) { // For reasoning deltas, do not stream to history. Accumulate the // current reasoning block and extract the first bold element @@ -954,6 +1013,10 @@ impl ChatWidget { fn on_task_started(&mut self) { self.agent_turn_running = true; self.saw_plan_update_this_turn = false; + self.saw_plan_item_this_turn = false; + self.plan_delta_buffer.clear(); + self.plan_item_active = false; + self.plan_stream_controller = None; self.bottom_pane.clear_quit_shortcut_hint(); self.quit_shortcut_expires_at = None; self.quit_shortcut_key = None; @@ -969,6 +1032,11 @@ impl ChatWidget { fn on_task_complete(&mut self, last_agent_message: Option, from_replay: bool) { // If a stream is currently active, finalize it. self.flush_answer_stream_with_separator(); + if let Some(mut controller) = self.plan_stream_controller.take() + && let Some(cell) = controller.finalize() + { + self.add_boxed_history(cell); + } self.flush_unified_exec_wait_streak(); // Mark task stopped and request redraw now that all content is in history. self.agent_turn_running = false; @@ -981,7 +1049,7 @@ impl ChatWidget { self.request_redraw(); if !from_replay && self.queued_user_messages.is_empty() { - self.maybe_prompt_plan_implementation(last_agent_message.as_deref()); + self.maybe_prompt_plan_implementation(); } // If there is a queued user message, send exactly one now to begin the next turn. self.maybe_send_next_queued_input(); @@ -993,7 +1061,7 @@ impl ChatWidget { self.maybe_show_pending_rate_limit_prompt(); } - fn maybe_prompt_plan_implementation(&mut self, last_agent_message: Option<&str>) { + fn maybe_prompt_plan_implementation(&mut self) { if !self.collaboration_modes_enabled() { return; } @@ -1003,8 +1071,7 @@ impl ChatWidget { if self.active_mode_kind() != ModeKind::Plan { return; } - let has_message = last_agent_message.is_some_and(|message| !message.trim().is_empty()); - if !has_message && !self.saw_plan_update_this_turn { + if !self.saw_plan_item_this_turn { return; } if !self.bottom_pane.no_modal_or_popup_active() { @@ -1749,15 +1816,28 @@ impl ChatWidget { /// Periodic tick to commit at most one queued line to history with a small delay, /// animating the output. pub(crate) fn on_commit_tick(&mut self) { + let mut has_controller = false; + let mut all_idle = true; if let Some(controller) = self.stream_controller.as_mut() { + has_controller = true; let (cell, is_idle) = controller.on_commit_tick(); if let Some(cell) = cell { self.bottom_pane.hide_status_indicator(); self.add_boxed_history(cell); } - if is_idle { - self.app_event_tx.send(AppEvent::StopCommitAnimation); + all_idle &= is_idle; + } + if let Some(controller) = self.plan_stream_controller.as_mut() { + has_controller = true; + let (cell, is_idle) = controller.on_commit_tick(); + if let Some(cell) = cell { + self.bottom_pane.hide_status_indicator(); + self.add_boxed_history(cell); } + all_idle &= is_idle; + } + if has_controller && all_idle { + self.app_event_tx.send(AppEvent::StopCommitAnimation); } } @@ -2160,6 +2240,7 @@ impl ChatWidget { rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, stream_controller: None, + plan_stream_controller: None, running_commands: HashMap::new(), suppressed_exec_calls: HashSet::new(), last_unified_wait: None, @@ -2188,6 +2269,9 @@ impl ChatWidget { needs_final_message_separator: false, had_work_activity: false, saw_plan_update_this_turn: false, + saw_plan_item_this_turn: false, + plan_delta_buffer: String::new(), + plan_item_active: false, last_separator_elapsed_secs: None, last_rendered_width: std::cell::Cell::new(None), feedback, @@ -2301,6 +2385,7 @@ impl ChatWidget { rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, stream_controller: None, + plan_stream_controller: None, running_commands: HashMap::new(), suppressed_exec_calls: HashSet::new(), last_unified_wait: None, @@ -2319,6 +2404,9 @@ impl ChatWidget { thread_name: None, forked_from: None, saw_plan_update_this_turn: false, + saw_plan_item_this_turn: false, + plan_delta_buffer: String::new(), + plan_item_active: false, queued_user_messages: VecDeque::new(), show_welcome_banner: is_first_run, suppress_session_configured_redraw: false, @@ -2431,6 +2519,7 @@ impl ChatWidget { rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, stream_controller: None, + plan_stream_controller: None, running_commands: HashMap::new(), suppressed_exec_calls: HashSet::new(), last_unified_wait: None, @@ -2459,6 +2548,9 @@ impl ChatWidget { needs_final_message_separator: false, had_work_activity: false, saw_plan_update_this_turn: false, + saw_plan_item_this_turn: false, + plan_delta_buffer: String::new(), + plan_item_active: false, last_separator_elapsed_secs: None, last_rendered_width: std::cell::Cell::new(None), feedback, @@ -3219,6 +3311,7 @@ impl ChatWidget { match msg { EventMsg::AgentMessageDelta(_) + | EventMsg::PlanDelta(_) | EventMsg::AgentReasoningDelta(_) | EventMsg::TerminalInteraction(_) | EventMsg::ExecCommandOutputDelta(_) => {} @@ -3234,6 +3327,7 @@ impl ChatWidget { EventMsg::AgentMessageDelta(AgentMessageDeltaEvent { delta }) => { self.on_agent_message_delta(delta) } + EventMsg::PlanDelta(event) => self.on_plan_delta(event.delta), EventMsg::AgentReasoningDelta(AgentReasoningDeltaEvent { delta }) | EventMsg::AgentReasoningRawContentDelta(AgentReasoningRawContentDeltaEvent { delta, @@ -3357,11 +3451,15 @@ impl ChatWidget { EventMsg::ThreadRolledBack(_) => {} EventMsg::RawResponseItem(_) | EventMsg::ItemStarted(_) - | EventMsg::ItemCompleted(_) | EventMsg::AgentMessageContentDelta(_) | EventMsg::ReasoningContentDelta(_) | EventMsg::ReasoningRawContentDelta(_) | EventMsg::DynamicToolCallRequest(_) => {} + EventMsg::ItemCompleted(event) => { + if let codex_protocol::items::TurnItem::Plan(plan_item) = event.item { + self.on_plan_item_completed(plan_item.text); + } + } } } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 331afdf90a..d24504578c 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -809,6 +809,7 @@ async fn make_chatwidget_manual( rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, stream_controller: None, + plan_stream_controller: None, running_commands: HashMap::new(), suppressed_exec_calls: HashSet::new(), skills_all: Vec::new(), @@ -840,6 +841,9 @@ async fn make_chatwidget_manual( needs_final_message_separator: false, had_work_activity: false, saw_plan_update_this_turn: false, + saw_plan_item_this_turn: false, + plan_delta_buffer: String::new(), + plan_item_active: false, last_separator_elapsed_secs: None, last_rendered_width: std::cell::Cell::new(None), feedback: codex_feedback::CodexFeedback::new(), @@ -1277,7 +1281,7 @@ async fn plan_implementation_popup_skips_when_messages_queued() { } #[tokio::test] -async fn plan_implementation_popup_shows_on_plan_update_without_message() { +async fn plan_implementation_popup_skips_without_proposed_plan() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; chat.set_feature_enabled(Feature::CollaborationModes, true); let plan_mask = @@ -1295,10 +1299,31 @@ async fn plan_implementation_popup_shows_on_plan_update_without_message() { }); chat.on_task_complete(None, false); + let popup = render_bottom_popup(&chat, 80); + assert!( + !popup.contains(PLAN_IMPLEMENTATION_TITLE), + "expected no plan popup without proposed plan output, got {popup:?}" + ); +} + +#[tokio::test] +async fn plan_implementation_popup_shows_after_proposed_plan_output() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; + chat.set_feature_enabled(Feature::CollaborationModes, true); + let plan_mask = + collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan) + .expect("expected plan collaboration mask"); + chat.set_collaboration_mask(plan_mask); + + chat.on_task_started(); + chat.on_plan_delta("- Step 1\n- Step 2\n".to_string()); + chat.on_plan_item_completed("- Step 1\n- Step 2\n".to_string()); + chat.on_task_complete(None, false); + let popup = render_bottom_popup(&chat, 80); assert!( popup.contains(PLAN_IMPLEMENTATION_TITLE), - "expected plan popup after plan update, got {popup:?}" + "expected plan popup after proposed plan output, got {popup:?}" ); } @@ -1957,6 +1982,7 @@ async fn unified_exec_wait_after_final_agent_message_snapshot() { id: "turn-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); @@ -1991,6 +2017,7 @@ async fn unified_exec_wait_before_streamed_agent_message_snapshot() { id: "turn-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); @@ -2779,6 +2806,7 @@ async fn interrupted_turn_error_message_snapshot() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); @@ -3793,6 +3821,7 @@ async fn interrupt_clears_unified_exec_wait_streak_snapshot() { id: "turn-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); @@ -3866,6 +3895,7 @@ async fn ui_snapshots_small_heights_task_running() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); chat.handle_codex_event(Event { @@ -3897,6 +3927,7 @@ async fn status_widget_and_approval_modal_snapshot() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); // Provide a deterministic header for the status line. @@ -3949,6 +3980,7 @@ async fn status_widget_active_snapshot() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); // Provide a deterministic header via a bold reasoning chunk. @@ -3998,6 +4030,7 @@ async fn mcp_startup_complete_does_not_clear_running_task() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); @@ -4554,6 +4587,7 @@ async fn stream_recovery_restores_previous_status_header() { id: "task".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); drain_insert_history(&mut rx); @@ -4591,6 +4625,7 @@ async fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { id: "s1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); @@ -4785,6 +4820,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() { id: "t1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); chat.handle_codex_event(Event { @@ -4832,6 +4868,7 @@ async fn chatwidget_markdown_code_blocks_vt100_snapshot() { id: "t1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); // Build a vt100 visual from the history insertions only (no UI overlay) @@ -4921,6 +4958,7 @@ async fn chatwidget_tall() { id: "t1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, + collaboration_mode_kind: ModeKind::Custom, }), }); for i in 0..30 { diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 1f19c929a2..aadfb318f8 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -25,6 +25,7 @@ use crate::render::line_utils::line_to_static; use crate::render::line_utils::prefix_lines; use crate::render::line_utils::push_owned_lines; use crate::render::renderable::Renderable; +use crate::style::proposed_plan_style; use crate::style::user_message_style; use crate::text_formatting::format_and_truncate_tool_result; use crate::text_formatting::truncate_text; @@ -1768,6 +1769,63 @@ pub(crate) fn new_plan_update(update: UpdatePlanArgs) -> PlanUpdateCell { PlanUpdateCell { explanation, plan } } +pub(crate) fn new_proposed_plan(plan_markdown: String) -> ProposedPlanCell { + ProposedPlanCell { plan_markdown } +} + +pub(crate) fn new_proposed_plan_stream( + lines: Vec>, + is_stream_continuation: bool, +) -> ProposedPlanStreamCell { + ProposedPlanStreamCell { + lines, + is_stream_continuation, + } +} + +#[derive(Debug)] +pub(crate) struct ProposedPlanCell { + plan_markdown: String, +} + +#[derive(Debug)] +pub(crate) struct ProposedPlanStreamCell { + lines: Vec>, + is_stream_continuation: bool, +} + +impl HistoryCell for ProposedPlanCell { + fn display_lines(&self, width: u16) -> Vec> { + let mut lines: Vec> = Vec::new(); + lines.push(vec!["• ".dim(), "Proposed Plan".bold()].into()); + lines.push(Line::from(" ")); + + let mut plan_lines: Vec> = vec![Line::from(" ")]; + let plan_style = proposed_plan_style(); + let wrap_width = width.saturating_sub(4).max(1) as usize; + let mut body: Vec> = Vec::new(); + append_markdown(&self.plan_markdown, Some(wrap_width), &mut body); + if body.is_empty() { + body.push(Line::from("(empty)".dim().italic())); + } + plan_lines.extend(prefix_lines(body, " ".into(), " ".into())); + plan_lines.push(Line::from(" ")); + + lines.extend(plan_lines.into_iter().map(|line| line.style(plan_style))); + lines + } +} + +impl HistoryCell for ProposedPlanStreamCell { + fn display_lines(&self, _width: u16) -> Vec> { + self.lines.clone() + } + + fn is_stream_continuation(&self) -> bool { + self.is_stream_continuation + } +} + #[derive(Debug)] pub(crate) struct PlanUpdateCell { explanation: Option, diff --git a/codex-rs/tui/src/streaming/controller.rs b/codex-rs/tui/src/streaming/controller.rs index e73ebd41e6..462962e980 100644 --- a/codex-rs/tui/src/streaming/controller.rs +++ b/codex-rs/tui/src/streaming/controller.rs @@ -1,5 +1,8 @@ use crate::history_cell::HistoryCell; use crate::history_cell::{self}; +use crate::render::line_utils::prefix_lines; +use crate::style::proposed_plan_style; +use ratatui::prelude::Stylize; use ratatui::text::Line; use super::StreamState; @@ -80,6 +83,106 @@ impl StreamController { } } +/// Controller that streams proposed plan markdown into a styled plan block. +pub(crate) struct PlanStreamController { + state: StreamState, + header_emitted: bool, + top_padding_emitted: bool, +} + +impl PlanStreamController { + pub(crate) fn new(width: Option) -> Self { + Self { + state: StreamState::new(width), + header_emitted: false, + top_padding_emitted: false, + } + } + + /// Push a delta; if it contains a newline, commit completed lines and start animation. + pub(crate) fn push(&mut self, delta: &str) -> bool { + let state = &mut self.state; + if !delta.is_empty() { + state.has_seen_delta = true; + } + state.collector.push_delta(delta); + if delta.contains('\n') { + let newly_completed = state.collector.commit_complete_lines(); + if !newly_completed.is_empty() { + state.enqueue(newly_completed); + return true; + } + } + false + } + + /// Finalize the active stream. Drain and emit now. + pub(crate) fn finalize(&mut self) -> Option> { + let remaining = { + let state = &mut self.state; + state.collector.finalize_and_drain() + }; + let mut out_lines = Vec::new(); + { + let state = &mut self.state; + if !remaining.is_empty() { + state.enqueue(remaining); + } + let step = state.drain_all(); + out_lines.extend(step); + } + + self.state.clear(); + self.emit(out_lines, true) + } + + /// Step animation: commit at most one queued line and handle end-of-drain cleanup. + pub(crate) fn on_commit_tick(&mut self) -> (Option>, bool) { + let step = self.state.step(); + (self.emit(step, false), self.state.is_idle()) + } + + fn emit( + &mut self, + lines: Vec>, + include_bottom_padding: bool, + ) -> Option> { + if lines.is_empty() && !include_bottom_padding { + return None; + } + + let mut out_lines: Vec> = Vec::new(); + let is_stream_continuation = self.header_emitted; + if !self.header_emitted { + out_lines.push(vec!["• ".dim(), "Proposed Plan".bold()].into()); + out_lines.push(Line::from(" ")); + self.header_emitted = true; + } + + let mut plan_lines: Vec> = Vec::new(); + if !self.top_padding_emitted { + plan_lines.push(Line::from(" ")); + self.top_padding_emitted = true; + } + plan_lines.extend(lines); + if include_bottom_padding { + plan_lines.push(Line::from(" ")); + } + + let plan_style = proposed_plan_style(); + let plan_lines = prefix_lines(plan_lines, " ".into(), " ".into()) + .into_iter() + .map(|line| line.style(plan_style)) + .collect::>(); + out_lines.extend(plan_lines); + + Some(Box::new(history_cell::new_proposed_plan_stream( + out_lines, + is_stream_continuation, + ))) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/codex-rs/tui/src/style.rs b/codex-rs/tui/src/style.rs index 26a71bd897..2b4ab4c4d3 100644 --- a/codex-rs/tui/src/style.rs +++ b/codex-rs/tui/src/style.rs @@ -9,6 +9,10 @@ pub fn user_message_style() -> Style { user_message_style_for(default_bg()) } +pub fn proposed_plan_style() -> Style { + proposed_plan_style_for(default_bg()) +} + /// Returns the style for a user-authored message using the provided terminal background. pub fn user_message_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style { match terminal_bg { @@ -17,6 +21,13 @@ pub fn user_message_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style { } } +pub fn proposed_plan_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style { + match terminal_bg { + Some(bg) => Style::default().bg(proposed_plan_bg(bg)), + None => Style::default(), + } +} + #[allow(clippy::disallowed_methods)] pub fn user_message_bg(terminal_bg: (u8, u8, u8)) -> Color { let (top, alpha) = if is_light(terminal_bg) { @@ -26,3 +37,13 @@ pub fn user_message_bg(terminal_bg: (u8, u8, u8)) -> Color { }; best_color(blend(top, terminal_bg, alpha)) } + +#[allow(clippy::disallowed_methods)] +pub fn proposed_plan_bg(terminal_bg: (u8, u8, u8)) -> Color { + let (top, alpha) = if is_light(terminal_bg) { + ((0, 110, 150), 0.08) + } else { + ((80, 170, 220), 0.2) + }; + best_color(blend(top, terminal_bg, alpha)) +} From 2d10aa68598591bf6e4eb2261cfe534852dd8d9a Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Fri, 30 Jan 2026 11:15:21 -0800 Subject: [PATCH 012/139] Tui: hide Code mode footer label (#10063) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Title Hide Code mode footer label/cycle hint; add Plan footer-collapse snapshots Summary - Keep Code mode internal naming but suppress the footer mode label + cycle hint when Code is active. - Only show the cycle hint when a non‑Code mode indicator is present. - Add Plan-mode footer collapse snapshot coverage (empty + queued, across widths) and update existing footer collapse snapshots for the new Code behavior. Notes - The test run currently fails in codex-cloud-requirements on origin/main due to a stale auth.mode field; no fix is included in this PR to keep the diff minimal. Codex author `codex resume 019c0296-cfd4-7193-9b0a-6949048e4546` --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 118 ++++++++++++++++-- codex-rs/tui/src/bottom_pane/footer.rs | 9 +- ...er__tests__footer_collapse_empty_full.snap | 2 +- ...ollapse_empty_mode_cycle_with_context.snap | 2 +- ...apse_empty_mode_cycle_without_context.snap | 2 +- ...ests__footer_collapse_empty_mode_only.snap | 2 +- ...ests__footer_collapse_plan_empty_full.snap | 13 ++ ...se_plan_empty_mode_cycle_with_context.snap | 13 ++ ...plan_empty_mode_cycle_without_context.snap | 13 ++ ..._footer_collapse_plan_empty_mode_only.snap | 13 ++ ...ests__footer_collapse_plan_queue_full.snap | 13 ++ ...se_plan_queue_message_without_context.snap | 13 ++ ..._footer_collapse_plan_queue_mode_only.snap | 13 ++ ...ollapse_plan_queue_short_with_context.snap | 13 ++ ...apse_plan_queue_short_without_context.snap | 13 ++ ...er__tests__footer_collapse_queue_full.snap | 2 +- ...ollapse_queue_message_without_context.snap | 2 +- ...ests__footer_collapse_queue_mode_only.snap | 2 +- ...ter_collapse_queue_short_with_context.snap | 2 +- ..._collapse_queue_short_without_context.snap | 2 +- codex-rs/tui/src/chatwidget.rs | 2 +- 21 files changed, 237 insertions(+), 27 deletions(-) create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_full.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_with_context.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_without_context.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_only.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_full.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_message_without_context.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_mode_only.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_with_context.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_without_context.snap diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 7c8b7585ac..1d3cf942fb 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -2873,7 +2873,8 @@ impl ChatComposer { } ActivePopup::None => { let footer_props = self.footer_props(); - let show_cycle_hint = !footer_props.is_task_running; + let show_cycle_hint = + !footer_props.is_task_running && self.collaboration_mode_indicator.is_some(); let show_shortcuts_hint = match footer_props.mode { FooterMode::ComposerEmpty => !self.is_in_paste_burst(), FooterMode::QuitShortcutReminder @@ -3353,23 +3354,23 @@ mod tests { fn setup_collab_footer( composer: &mut ChatComposer, context_percent: i64, - indicator: CollaborationModeIndicator, + indicator: Option, ) { composer.set_collaboration_modes_enabled(true); - composer.set_collaboration_mode_indicator(Some(indicator)); + composer.set_collaboration_mode_indicator(indicator); composer.set_context_window(Some(context_percent), None); } - // Empty textarea, agent idle: shortcuts hint can show, and cycle hint is available. + // Empty textarea, agent idle: shortcuts hint can show, and cycle hint is hidden. snapshot_composer_state_with_width("footer_collapse_empty_full", 120, true, |composer| { - setup_collab_footer(composer, 100, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 100, None); }); snapshot_composer_state_with_width( "footer_collapse_empty_mode_cycle_with_context", 60, true, |composer| { - setup_collab_footer(composer, 100, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 100, None); }, ); snapshot_composer_state_with_width( @@ -3377,7 +3378,7 @@ mod tests { 44, true, |composer| { - setup_collab_footer(composer, 100, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 100, None); }, ); snapshot_composer_state_with_width( @@ -3385,13 +3386,47 @@ mod tests { 26, true, |composer| { - setup_collab_footer(composer, 100, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 100, None); + }, + ); + + // Empty textarea, plan mode idle: shortcuts hint and cycle hint are available. + snapshot_composer_state_with_width( + "footer_collapse_plan_empty_full", + 120, + true, + |composer| { + setup_collab_footer(composer, 100, Some(CollaborationModeIndicator::Plan)); + }, + ); + snapshot_composer_state_with_width( + "footer_collapse_plan_empty_mode_cycle_with_context", + 60, + true, + |composer| { + setup_collab_footer(composer, 100, Some(CollaborationModeIndicator::Plan)); + }, + ); + snapshot_composer_state_with_width( + "footer_collapse_plan_empty_mode_cycle_without_context", + 44, + true, + |composer| { + setup_collab_footer(composer, 100, Some(CollaborationModeIndicator::Plan)); + }, + ); + snapshot_composer_state_with_width( + "footer_collapse_plan_empty_mode_only", + 26, + true, + |composer| { + setup_collab_footer(composer, 100, Some(CollaborationModeIndicator::Plan)); }, ); // Textarea has content, agent running, steer enabled: queue hint is shown. snapshot_composer_state_with_width("footer_collapse_queue_full", 120, true, |composer| { - setup_collab_footer(composer, 98, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 98, None); composer.set_steer_enabled(true); composer.set_task_running(true); composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); @@ -3401,7 +3436,7 @@ mod tests { 50, true, |composer| { - setup_collab_footer(composer, 98, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 98, None); composer.set_steer_enabled(true); composer.set_task_running(true); composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); @@ -3412,7 +3447,7 @@ mod tests { 40, true, |composer| { - setup_collab_footer(composer, 98, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 98, None); composer.set_steer_enabled(true); composer.set_task_running(true); composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); @@ -3423,7 +3458,7 @@ mod tests { 30, true, |composer| { - setup_collab_footer(composer, 98, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 98, None); composer.set_steer_enabled(true); composer.set_task_running(true); composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); @@ -3434,7 +3469,64 @@ mod tests { 20, true, |composer| { - setup_collab_footer(composer, 98, CollaborationModeIndicator::Code); + setup_collab_footer(composer, 98, None); + composer.set_steer_enabled(true); + composer.set_task_running(true); + composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); + }, + ); + + // Textarea has content, plan mode active, agent running, steer enabled: queue hint + mode. + snapshot_composer_state_with_width( + "footer_collapse_plan_queue_full", + 120, + true, + |composer| { + setup_collab_footer(composer, 98, Some(CollaborationModeIndicator::Plan)); + composer.set_steer_enabled(true); + composer.set_task_running(true); + composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); + }, + ); + snapshot_composer_state_with_width( + "footer_collapse_plan_queue_short_with_context", + 50, + true, + |composer| { + setup_collab_footer(composer, 98, Some(CollaborationModeIndicator::Plan)); + composer.set_steer_enabled(true); + composer.set_task_running(true); + composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); + }, + ); + snapshot_composer_state_with_width( + "footer_collapse_plan_queue_message_without_context", + 40, + true, + |composer| { + setup_collab_footer(composer, 98, Some(CollaborationModeIndicator::Plan)); + composer.set_steer_enabled(true); + composer.set_task_running(true); + composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); + }, + ); + snapshot_composer_state_with_width( + "footer_collapse_plan_queue_short_without_context", + 30, + true, + |composer| { + setup_collab_footer(composer, 98, Some(CollaborationModeIndicator::Plan)); + composer.set_steer_enabled(true); + composer.set_task_running(true); + composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); + }, + ); + snapshot_composer_state_with_width( + "footer_collapse_plan_queue_mode_only", + 20, + true, + |composer| { + setup_collab_footer(composer, 98, Some(CollaborationModeIndicator::Plan)); composer.set_steer_enabled(true); composer.set_task_running(true); composer.set_text_content("Test".to_string(), Vec::new(), Vec::new()); diff --git a/codex-rs/tui/src/bottom_pane/footer.rs b/codex-rs/tui/src/bottom_pane/footer.rs index 1ded8ce319..c297b9e362 100644 --- a/codex-rs/tui/src/bottom_pane/footer.rs +++ b/codex-rs/tui/src/bottom_pane/footer.rs @@ -73,12 +73,12 @@ pub(crate) struct FooterProps { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum CollaborationModeIndicator { Plan, - Code, PairProgramming, Execute, } const MODE_CYCLE_HINT: &str = "shift+tab to cycle"; +const FOOTER_CONTEXT_GAP_COLS: u16 = 1; impl CollaborationModeIndicator { fn label(self, show_cycle_hint: bool) -> String { @@ -89,7 +89,6 @@ impl CollaborationModeIndicator { }; match self { CollaborationModeIndicator::Plan => format!("Plan mode{suffix}"), - CollaborationModeIndicator::Code => format!("Code mode{suffix}"), CollaborationModeIndicator::PairProgramming => { format!("Pair Programming mode{suffix}") } @@ -101,7 +100,6 @@ impl CollaborationModeIndicator { let label = self.label(show_cycle_hint); match self { CollaborationModeIndicator::Plan => Span::from(label).magenta(), - CollaborationModeIndicator::Code => Span::from(label).dim(), CollaborationModeIndicator::PairProgramming => Span::from(label).cyan(), CollaborationModeIndicator::Execute => Span::from(label).dim(), } @@ -475,7 +473,10 @@ pub(crate) fn can_show_left_with_context(area: Rect, left_width: u16, context_wi let Some(context_x) = right_aligned_x(area, context_width) else { return true; }; - let left_extent = FOOTER_INDENT_COLS as u16 + left_width; + if left_width == 0 { + return true; + } + let left_extent = FOOTER_INDENT_COLS as u16 + left_width + FOOTER_CONTEXT_GAP_COLS; left_extent <= context_x.saturating_sub(area.x) } diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_full.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_full.snap index 90a7b12e4a..4324d806e2 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_full.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_full.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" ? for shortcuts · Code mode (shift+tab to cycle) 100% context left " +" ? for shortcuts 100% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_with_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_with_context.snap index cfc93be1e6..ecaeb58149 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_with_context.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_with_context.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" Code mode (shift+tab to cycle) 100% context left " +" ? for shortcuts 100% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_without_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_without_context.snap index 9901a8fbbf..118ac25291 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_without_context.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_cycle_without_context.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" Code mode (shift+tab to cycle) " +" ? for shortcuts 100% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_only.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_only.snap index 98c7c33889..9c95004785 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_only.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_empty_mode_only.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" Code mode " +" 100% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_full.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_full.snap new file mode 100644 index 0000000000..f39aefad64 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_full.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Ask Codex to do anything " +" " +" " +" " +" " +" " +" " +" ? for shortcuts · Plan mode (shift+tab to cycle) 100% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_with_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_with_context.snap new file mode 100644 index 0000000000..347ba31648 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_with_context.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Ask Codex to do anything " +" " +" " +" " +" " +" " +" " +" Plan mode (shift+tab to cycle) 100% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_without_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_without_context.snap new file mode 100644 index 0000000000..006e2a1773 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_cycle_without_context.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Ask Codex to do anything " +" " +" " +" " +" " +" " +" " +" Plan mode (shift+tab to cycle) " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_only.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_only.snap new file mode 100644 index 0000000000..bea268c57e --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_empty_mode_only.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Ask Codex to do anythin " +" " +" " +" " +" " +" " +" " +" Plan mode " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_full.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_full.snap new file mode 100644 index 0000000000..5f0f353822 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_full.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Test " +" " +" " +" " +" " +" " +" " +" tab to queue message · Plan mode 98% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_message_without_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_message_without_context.snap new file mode 100644 index 0000000000..017e3eb2aa --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_message_without_context.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Test " +" " +" " +" " +" " +" " +" " +" tab to queue message · Plan mode " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_mode_only.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_mode_only.snap new file mode 100644 index 0000000000..35a94ac73a --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_mode_only.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Test " +" " +" " +" " +" " +" " +" " +" Plan mode " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_with_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_with_context.snap new file mode 100644 index 0000000000..77f38dc4e7 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_with_context.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Test " +" " +" " +" " +" " +" " +" " +" tab to queue · Plan mode 98% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_without_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_without_context.snap new file mode 100644 index 0000000000..91f917e987 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_plan_queue_short_without_context.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/chat_composer.rs +expression: terminal.backend() +--- +" " +"› Test " +" " +" " +" " +" " +" " +" " +" tab to queue · Plan mode " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_full.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_full.snap index cfb47fa9d7..1057803326 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_full.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_full.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" tab to queue message · Code mode 98% context left " +" tab to queue message 98% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_message_without_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_message_without_context.snap index 0a91488b58..4f44c0424e 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_message_without_context.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_message_without_context.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" tab to queue message · Code mode " +" tab to queue 98% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_mode_only.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_mode_only.snap index 9adcc6e059..e2d1d2e282 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_mode_only.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_mode_only.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" Code mode " +" tab to queue " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_with_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_with_context.snap index 0910602616..b7128fd415 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_with_context.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_with_context.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" tab to queue · Code mode 98% context left " +" tab to queue message 98% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_without_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_without_context.snap index 993dc34035..3df7f74328 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_without_context.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_collapse_queue_short_without_context.snap @@ -10,4 +10,4 @@ expression: terminal.backend() " " " " " " -" tab to queue · Code mode " +" tab to queue message " diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 7793f0a39c..b0c8c7cf33 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5313,7 +5313,7 @@ impl ChatWidget { } match self.active_mode_kind() { ModeKind::Plan => Some(CollaborationModeIndicator::Plan), - ModeKind::Code => Some(CollaborationModeIndicator::Code), + ModeKind::Code => None, ModeKind::PairProgramming => Some(CollaborationModeIndicator::PairProgramming), ModeKind::Execute => Some(CollaborationModeIndicator::Execute), ModeKind::Custom => None, From e6d913af2dc5ace19c06d5ae6a35c34cff996a1c Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 30 Jan 2026 11:18:39 -0800 Subject: [PATCH 013/139] chore: rename ChatGpt -> Chatgpt in type names (#10244) When using ChatGPT in names of types, we should be consistent, so this renames some types with `ChatGpt` in the name to `Chatgpt`. From https://rust-lang.github.io/api-guidelines/naming.html: > In `UpperCamelCase`, acronyms and contractions of compound words count as one word: use `Uuid` rather than `UUID`, `Usize` rather than `USize` or `Stdin` rather than `StdIn`. In `snake_case`, acronyms and contractions are lower-cased: `is_xid_start`. This PR updates existing uses of `ChatGpt` and changes them to `Chatgpt`. Though in all cases where it could affect the wire format, I visually inspected that we don't change anything there. That said, this _will_ change the codegen because it will affect the spelling of type names. For example, this renames `AuthMode::ChatGPT` to `AuthMode::Chatgpt` in `app-server-protocol`, but the wire format is still `"chatgpt"`. This PR also updates a number of types in `codex-rs/core/src/auth.rs`. --- .../src/protocol/common.rs | 2 +- .../app-server/src/codex_message_processor.rs | 2 +- .../app-server/tests/common/auth_fixtures.rs | 2 +- codex-rs/cli/src/login.rs | 2 +- codex-rs/core/src/auth.rs | 66 +++++++++---------- codex-rs/core/src/auth/storage.rs | 2 +- codex-rs/core/src/codex.rs | 2 +- codex-rs/core/src/model_provider_info.rs | 2 +- codex-rs/core/src/models_manager/manager.rs | 2 +- codex-rs/core/tests/responses_headers.rs | 4 +- codex-rs/core/tests/suite/auth_refresh.rs | 18 ++--- codex-rs/login/src/server.rs | 2 +- codex-rs/tui/src/onboarding/auth.rs | 2 +- codex-rs/tui/src/status/helpers.rs | 2 +- 14 files changed, 55 insertions(+), 55 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index efcc651b33..eb59480b58 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -30,7 +30,7 @@ pub enum AuthMode { /// OpenAI API key provided by the caller and stored by Codex. ApiKey, /// ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex). - ChatGPT, + Chatgpt, /// [UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. /// /// ChatGPT auth tokens are supplied by an external host app and are only diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index c02f3c7368..78f670c049 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1246,7 +1246,7 @@ impl CodexMessageProcessor { let account = match self.auth_manager.auth_cached() { Some(auth) => Some(match auth { CodexAuth::ApiKey(_) => Account::ApiKey {}, - CodexAuth::ChatGpt(_) | CodexAuth::ChatGptAuthTokens(_) => { + CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) => { let email = auth.get_account_email(); let plan_type = auth.account_plan_type(); diff --git a/codex-rs/app-server/tests/common/auth_fixtures.rs b/codex-rs/app-server/tests/common/auth_fixtures.rs index b78d5b105d..e689e41838 100644 --- a/codex-rs/app-server/tests/common/auth_fixtures.rs +++ b/codex-rs/app-server/tests/common/auth_fixtures.rs @@ -159,7 +159,7 @@ pub fn write_chatgpt_auth( let last_refresh = fixture.last_refresh.unwrap_or_else(|| Some(Utc::now())); let auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(tokens), last_refresh, diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index cee002f339..01a830acb2 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -236,7 +236,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { std::process::exit(1); } }, - AuthMode::ChatGPT => { + AuthMode::Chatgpt => { eprintln!("Logged in using ChatGPT"); std::process::exit(0); } diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index 4bddbe3031..fb4114a7c2 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -44,15 +44,15 @@ use thiserror::Error; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AuthMode { ApiKey, - ChatGPT, + Chatgpt, } /// Authentication mechanism used by the current user. #[derive(Debug, Clone)] pub enum CodexAuth { ApiKey(ApiKeyAuth), - ChatGpt(ChatGptAuth), - ChatGptAuthTokens(ChatGptAuthTokens), + Chatgpt(ChatgptAuth), + ChatgptAuthTokens(ChatgptAuthTokens), } #[derive(Debug, Clone)] @@ -61,18 +61,18 @@ pub struct ApiKeyAuth { } #[derive(Debug, Clone)] -pub struct ChatGptAuth { - state: ChatGptAuthState, +pub struct ChatgptAuth { + state: ChatgptAuthState, storage: Arc, } #[derive(Debug, Clone)] -pub struct ChatGptAuthTokens { - state: ChatGptAuthState, +pub struct ChatgptAuthTokens { + state: ChatgptAuthState, } #[derive(Debug, Clone)] -struct ChatGptAuthState { +struct ChatgptAuthState { auth_dot_json: Arc>>, client: CodexHttpClient, } @@ -161,18 +161,18 @@ impl CodexAuth { } let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode); - let state = ChatGptAuthState { + let state = ChatgptAuthState { auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))), client, }; match auth_mode { - ApiAuthMode::ChatGPT => { + ApiAuthMode::Chatgpt => { let storage = create_auth_storage(codex_home.to_path_buf(), storage_mode); - Ok(Self::ChatGpt(ChatGptAuth { state, storage })) + Ok(Self::Chatgpt(ChatgptAuth { state, storage })) } ApiAuthMode::ChatgptAuthTokens => { - Ok(Self::ChatGptAuthTokens(ChatGptAuthTokens { state })) + Ok(Self::ChatgptAuthTokens(ChatgptAuthTokens { state })) } ApiAuthMode::ApiKey => unreachable!("api key mode is handled above"), } @@ -189,31 +189,31 @@ impl CodexAuth { pub fn internal_auth_mode(&self) -> AuthMode { match self { Self::ApiKey(_) => AuthMode::ApiKey, - Self::ChatGpt(_) | Self::ChatGptAuthTokens(_) => AuthMode::ChatGPT, + Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => AuthMode::Chatgpt, } } pub fn api_auth_mode(&self) -> ApiAuthMode { match self { Self::ApiKey(_) => ApiAuthMode::ApiKey, - Self::ChatGpt(_) => ApiAuthMode::ChatGPT, - Self::ChatGptAuthTokens(_) => ApiAuthMode::ChatgptAuthTokens, + Self::Chatgpt(_) => ApiAuthMode::Chatgpt, + Self::ChatgptAuthTokens(_) => ApiAuthMode::ChatgptAuthTokens, } } pub fn is_chatgpt_auth(&self) -> bool { - self.internal_auth_mode() == AuthMode::ChatGPT + self.internal_auth_mode() == AuthMode::Chatgpt } pub fn is_external_chatgpt_tokens(&self) -> bool { - matches!(self, Self::ChatGptAuthTokens(_)) + matches!(self, Self::ChatgptAuthTokens(_)) } /// Returns `None` is `is_internal_auth_mode() != AuthMode::ApiKey`. pub fn api_key(&self) -> Option<&str> { match self { Self::ApiKey(auth) => Some(auth.api_key.as_str()), - Self::ChatGpt(_) | Self::ChatGptAuthTokens(_) => None, + Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => None, } } @@ -234,7 +234,7 @@ impl CodexAuth { pub fn get_token(&self) -> Result { match self { Self::ApiKey(auth) => Ok(auth.api_key.clone()), - Self::ChatGpt(_) | Self::ChatGptAuthTokens(_) => { + Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => { let access_token = self.get_token_data()?.access_token; Ok(access_token) } @@ -278,8 +278,8 @@ impl CodexAuth { /// Returns `None` if `is_chatgpt_auth()` is false. fn get_current_auth_json(&self) -> Option { let state = match self { - Self::ChatGpt(auth) => &auth.state, - Self::ChatGptAuthTokens(auth) => &auth.state, + Self::Chatgpt(auth) => &auth.state, + Self::ChatgptAuthTokens(auth) => &auth.state, Self::ApiKey(_) => return None, }; #[expect(clippy::unwrap_used)] @@ -294,7 +294,7 @@ impl CodexAuth { /// Consider this private to integration tests. pub fn create_dummy_chatgpt_auth_for_testing() -> Self { let auth_dot_json = AuthDotJson { - auth_mode: Some(ApiAuthMode::ChatGPT), + auth_mode: Some(ApiAuthMode::Chatgpt), openai_api_key: None, tokens: Some(TokenData { id_token: Default::default(), @@ -306,12 +306,12 @@ impl CodexAuth { }; let client = crate::default_client::create_client(); - let state = ChatGptAuthState { + let state = ChatgptAuthState { auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))), client, }; let storage = create_auth_storage(PathBuf::new(), AuthCredentialsStoreMode::File); - Self::ChatGpt(ChatGptAuth { state, storage }) + Self::Chatgpt(ChatgptAuth { state, storage }) } fn from_api_key_with_client(api_key: &str, _client: CodexHttpClient) -> Self { @@ -325,7 +325,7 @@ impl CodexAuth { } } -impl ChatGptAuth { +impl ChatgptAuth { fn current_auth_json(&self) -> Option { #[expect(clippy::unwrap_used)] self.state.auth_dot_json.lock().unwrap().clone() @@ -436,8 +436,8 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { if let Some(required_method) = config.forced_login_method { let method_violation = match (required_method, auth.internal_auth_mode()) { (ForcedLoginMethod::Api, AuthMode::ApiKey) => None, - (ForcedLoginMethod::Chatgpt, AuthMode::ChatGPT) => None, - (ForcedLoginMethod::Api, AuthMode::ChatGPT) => Some( + (ForcedLoginMethod::Chatgpt, AuthMode::Chatgpt) => None, + (ForcedLoginMethod::Api, AuthMode::Chatgpt) => Some( "API key login is required, but ChatGPT is currently being used. Logging out." .to_string(), ), @@ -750,7 +750,7 @@ impl AuthDotJson { if self.openai_api_key.is_some() { return ApiAuthMode::ApiKey; } - ApiAuthMode::ChatGPT + ApiAuthMode::Chatgpt } fn storage_mode( @@ -1127,11 +1127,11 @@ impl AuthManager { None => return Ok(()), }; match auth { - CodexAuth::ChatGptAuthTokens(_) => { + CodexAuth::ChatgptAuthTokens(_) => { self.refresh_external_auth(ExternalAuthRefreshReason::Unauthorized) .await } - CodexAuth::ChatGpt(chatgpt_auth) => { + CodexAuth::Chatgpt(chatgpt_auth) => { let token_data = chatgpt_auth.current_token_data().ok_or_else(|| { RefreshTokenError::Transient(std::io::Error::other( "Token data is not available.", @@ -1170,7 +1170,7 @@ impl AuthManager { async fn refresh_if_stale(&self, auth: &CodexAuth) -> Result { let chatgpt_auth = match auth { - CodexAuth::ChatGpt(chatgpt_auth) => chatgpt_auth, + CodexAuth::Chatgpt(chatgpt_auth) => chatgpt_auth, _ => return Ok(false), }; @@ -1250,7 +1250,7 @@ impl AuthManager { async fn refresh_tokens( &self, - auth: &ChatGptAuth, + auth: &ChatgptAuth, refresh_token: String, ) -> Result<(), RefreshTokenError> { let refresh_response = try_refresh_token(refresh_token, auth.client()).await?; @@ -1373,7 +1373,7 @@ mod tests { .unwrap() .unwrap(); assert_eq!(None, auth.api_key()); - assert_eq!(AuthMode::ChatGPT, auth.internal_auth_mode()); + assert_eq!(AuthMode::Chatgpt, auth.internal_auth_mode()); let auth_dot_json = auth .get_current_auth_json() diff --git a/codex-rs/core/src/auth/storage.rs b/codex-rs/core/src/auth/storage.rs index b4f4bb8e7e..1ac1b2ee18 100644 --- a/codex-rs/core/src/auth/storage.rs +++ b/codex-rs/core/src/auth/storage.rs @@ -565,7 +565,7 @@ mod tests { let auth_file = get_auth_file(codex_home.path()); std::fs::write(&auth_file, "stale")?; let auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(TokenData { id_token: Default::default(), diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 30dd8d24fc..5a1ce90dd9 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -5271,7 +5271,7 @@ mod tests { model_info.slug.as_str(), None, Some("test@test.com".to_string()), - Some(AuthMode::ChatGPT), + Some(AuthMode::Chatgpt), false, "test".to_string(), session_source, diff --git a/codex-rs/core/src/model_provider_info.rs b/codex-rs/core/src/model_provider_info.rs index 0f5a8643a7..56ff76d7f6 100644 --- a/codex-rs/core/src/model_provider_info.rs +++ b/codex-rs/core/src/model_provider_info.rs @@ -137,7 +137,7 @@ impl ModelProviderInfo { &self, auth_mode: Option, ) -> crate::error::Result { - let default_base_url = if matches!(auth_mode, Some(AuthMode::ChatGPT)) { + let default_base_url = if matches!(auth_mode, Some(AuthMode::Chatgpt)) { "https://chatgpt.com/backend-api/codex" } else { "https://api.openai.com/v1" diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index 40f9c8ccdc..936e40d1bc 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -274,7 +274,7 @@ impl ModelsManager { let mut merged_presets = ModelPreset::merge(remote_presets, existing_presets); let chatgpt_mode = matches!( self.auth_manager.get_internal_auth_mode(), - Some(AuthMode::ChatGPT) + Some(AuthMode::Chatgpt) ); merged_presets = ModelPreset::filter_by_auth(merged_presets, chatgpt_mode); diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index b2c64818cc..86d6e8ce61 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -71,7 +71,7 @@ async fn responses_stream_includes_subagent_header_on_review() { let config = Arc::new(config); let conversation_id = ThreadId::new(); - let auth_mode = AuthMode::ChatGPT; + let auth_mode = AuthMode::Chatgpt; let session_source = SessionSource::SubAgent(SubAgentSource::Review); let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); let otel_manager = OtelManager::new( @@ -169,7 +169,7 @@ async fn responses_stream_includes_subagent_header_on_other() { let config = Arc::new(config); let conversation_id = ThreadId::new(); - let auth_mode = AuthMode::ChatGPT; + let auth_mode = AuthMode::Chatgpt; let session_source = SessionSource::SubAgent(SubAgentSource::Other("my-task".to_string())); let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); diff --git a/codex-rs/core/tests/suite/auth_refresh.rs b/codex-rs/core/tests/suite/auth_refresh.rs index 4ef3b82eec..a6be08f23c 100644 --- a/codex-rs/core/tests/suite/auth_refresh.rs +++ b/codex-rs/core/tests/suite/auth_refresh.rs @@ -51,7 +51,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> { let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(initial_tokens.clone()), last_refresh: Some(initial_last_refresh), @@ -113,7 +113,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> { let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(initial_tokens.clone()), last_refresh: Some(initial_last_refresh), @@ -159,7 +159,7 @@ async fn refreshes_token_when_last_refresh_is_stale() -> Result<()> { let stale_refresh = Utc::now() - Duration::days(9); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(initial_tokens.clone()), last_refresh: Some(stale_refresh), @@ -218,7 +218,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(initial_tokens.clone()), last_refresh: Some(initial_last_refresh), @@ -268,7 +268,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()> let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(initial_tokens.clone()), last_refresh: Some(initial_last_refresh), @@ -320,7 +320,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> { let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(initial_tokens.clone()), last_refresh: Some(initial_last_refresh), @@ -329,7 +329,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> { let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token"); let disk_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(disk_tokens.clone()), last_refresh: Some(initial_last_refresh), @@ -412,7 +412,7 @@ async fn unauthorized_recovery_skips_reload_on_account_mismatch() -> Result<()> let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(initial_tokens.clone()), last_refresh: Some(initial_last_refresh), @@ -427,7 +427,7 @@ async fn unauthorized_recovery_skips_reload_on_account_mismatch() -> Result<()> ..disk_tokens.clone() }; let disk_auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: None, tokens: Some(disk_tokens), last_refresh: Some(initial_last_refresh), diff --git a/codex-rs/login/src/server.rs b/codex-rs/login/src/server.rs index 5272c84eaa..e571d9b824 100644 --- a/codex-rs/login/src/server.rs +++ b/codex-rs/login/src/server.rs @@ -560,7 +560,7 @@ pub(crate) async fn persist_tokens_async( tokens.account_id = Some(acc.to_string()); } let auth = AuthDotJson { - auth_mode: Some(AuthMode::ChatGPT), + auth_mode: Some(AuthMode::Chatgpt), openai_api_key: api_key, tokens: Some(tokens), last_refresh: Some(Utc::now()), diff --git a/codex-rs/tui/src/onboarding/auth.rs b/codex-rs/tui/src/onboarding/auth.rs index daf64f36d8..7378b6628e 100644 --- a/codex-rs/tui/src/onboarding/auth.rs +++ b/codex-rs/tui/src/onboarding/auth.rs @@ -663,7 +663,7 @@ impl AuthModeWidget { fn handle_existing_chatgpt_login(&mut self) -> bool { if matches!( self.login_status, - LoginStatus::AuthMode(AuthMode::ChatGPT | AuthMode::ChatgptAuthTokens) + LoginStatus::AuthMode(AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens) ) { *self.sign_in_state.write().unwrap() = SignInState::ChatGptSuccess; self.request_frame.schedule_frame(); diff --git a/codex-rs/tui/src/status/helpers.rs b/codex-rs/tui/src/status/helpers.rs index 0a801227f5..bc4cbb9c16 100644 --- a/codex-rs/tui/src/status/helpers.rs +++ b/codex-rs/tui/src/status/helpers.rs @@ -91,7 +91,7 @@ pub(crate) fn compose_account_display( let auth = auth_manager.auth_cached()?; match auth { - CodexAuth::ChatGpt(_) | CodexAuth::ChatGptAuthTokens(_) => { + CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) => { let email = auth.get_account_email(); let plan = plan .map(|plan_type| title_case(format!("{plan_type:?}").as_str())) From 9b29a48a09893cfdea7d7ea056918a9a011ccc01 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 30 Jan 2026 11:48:03 -0800 Subject: [PATCH 014/139] Plan mode prompt (#10238) # External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request. --- .../core/templates/collaboration_mode/plan.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/templates/collaboration_mode/plan.md b/codex-rs/core/templates/collaboration_mode/plan.md index 395b6c368b..2ae72aba43 100644 --- a/codex-rs/core/templates/collaboration_mode/plan.md +++ b/codex-rs/core/templates/collaboration_mode/plan.md @@ -60,18 +60,14 @@ Do not ask questions that can be answered from the repo or system (for example, Every assistant turn MUST be exactly one of: A) a `request_user_input` tool call (questions/options only), OR -B) a non-final status update with no questions and no plan content, OR C) the final output: a titled, plan-only document. Rules: * No questions in free text (only via `request_user_input`). * Never mix a `request_user_input` call with plan content. -* Status updates must not include questions or plan content. * Internal tool/repo exploration is allowed privately before A, B, or C. -Status updates should be frequent during exploration. Provide 1-2 sentence updates that summarize discoveries, assumption changes, or why you are changing direction. Use Parallel tools for exploration. - ## Ask a lot, but never ask trivia You SHOULD ask many questions, but each question must: @@ -113,19 +109,15 @@ When you present the official plan, wrap it in a `` block so the Example: -# Plan title -- Step 1 -- Step 2 +plan content -The final plan must be plan-only and include: +plan content should be human and agent digestible. The final plan must be plan-only and include: * A clear title -* A TL;DR section (3–5 bullets) -* Exact file paths to change -* Exact structures or shapes to introduce or modify -* Exact function, method, type, and variable names and signatures -* Test cases +* tldr section. don't necessary call it tldr. +* Important changes or additions of signatures, structs, types. +* Test cases and scenarios * Explicit assumptions and defaults chosen where needed Do not ask "should I proceed?" in the final output. The user can easily switch out of Plan mode and request implementation if you have included a `` block in your response. Alternatively, they can decide to stay in Plan mode and continue refining the plan. From 2457bb3c404e4c092eef52ff9b2538b01623a17d Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Fri, 30 Jan 2026 11:57:13 -0800 Subject: [PATCH 015/139] Fix deploy (#10251) Fix https://github.com/openai/codex/actions/runs/21527697445/job/62035898666 --- codex-rs/tui/src/app.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index c65ce4498d..7ed9d0b035 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1154,6 +1154,7 @@ impl App { return Ok(AppExitInfo { token_usage: app.token_usage(), thread_id: app.chat_widget.thread_id(), + thread_name: app.chat_widget.thread_name(), update_action: app.pending_update_action, exit_reason, }); From b7351f7f53d07584bf292c1b61bb58c8aa839bb5 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 30 Jan 2026 12:22:37 -0800 Subject: [PATCH 016/139] plan prompt (#10255) # External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request. --- codex-rs/core/templates/collaboration_mode/plan.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/templates/collaboration_mode/plan.md b/codex-rs/core/templates/collaboration_mode/plan.md index 2ae72aba43..f600bc9c7b 100644 --- a/codex-rs/core/templates/collaboration_mode/plan.md +++ b/codex-rs/core/templates/collaboration_mode/plan.md @@ -32,7 +32,6 @@ Actions that gather truth, reduce ambiguity, or validate feasibility without cha Actions that implement the plan or change repo-tracked state. Examples: * Editing or writing files -* Generating, updating, or accepting snapshots * Running formatters or linters that rewrite files * Applying patches, migrations, or codegen that updates repo-tracked files * Side-effectful commands whose purpose is to carry out the plan rather than refine it @@ -60,13 +59,13 @@ Do not ask questions that can be answered from the repo or system (for example, Every assistant turn MUST be exactly one of: A) a `request_user_input` tool call (questions/options only), OR -C) the final output: a titled, plan-only document. +B) the final output: a titled, plan-only document. Rules: * No questions in free text (only via `request_user_input`). * Never mix a `request_user_input` call with plan content. -* Internal tool/repo exploration is allowed privately before A, B, or C. +* Internal tool/repo exploration is allowed privately before A or B. ## Ask a lot, but never ask trivia From 83317ed4bff93b5cf98d3d3ec82dad66dd5b2207 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Fri, 30 Jan 2026 12:39:15 -0800 Subject: [PATCH 017/139] Make plan highlight use popup grey background (#10253) ## Summary - align proposed plan background with popup surface color by reusing `user_message_bg` - remove the custom blue-tinted plan background image --- codex-rs/tui/src/style.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/codex-rs/tui/src/style.rs b/codex-rs/tui/src/style.rs index 2b4ab4c4d3..fc8fd4c501 100644 --- a/codex-rs/tui/src/style.rs +++ b/codex-rs/tui/src/style.rs @@ -40,10 +40,5 @@ pub fn user_message_bg(terminal_bg: (u8, u8, u8)) -> Color { #[allow(clippy::disallowed_methods)] pub fn proposed_plan_bg(terminal_bg: (u8, u8, u8)) -> Color { - let (top, alpha) = if is_light(terminal_bg) { - ((0, 110, 150), 0.08) - } else { - ((80, 170, 220), 0.2) - }; - best_color(blend(top, terminal_bg, alpha)) + user_message_bg(terminal_bg) } From dd6c1d3787aa3c8032f6e6496e2bf25c47ddb37a Mon Sep 17 00:00:00 2001 From: daniel-oai Date: Fri, 30 Jan 2026 12:42:07 -0800 Subject: [PATCH 018/139] Skip loading codex home as project layer (#10207) Summary: - Fixes issue #9932: https://github.com/openai/codex/issues/9932 - Prevents `$CODEX_HOME` (typically `~/.codex`) from being discovered as a project `.codex` layer by skipping it during project layer traversal. We compare both normalized absolute paths and best-effort canonicalized paths to handle symlinks. - Adds regression tests for home-directory invocation and for the case where `CODEX_HOME` points to a project `.codex` directory (e.g., worktrees/editor integrations). Testing: - `cargo build -p codex-cli --bin codex` - `cargo build -p codex-rmcp-client --bin test_stdio_server` - `cargo test -p codex-core` - `cargo test --all-features` - Manual: ran `target/debug/codex` from `~` and confirmed the disabled-folder warning and trust prompt no longer appear. --- codex-rs/core/src/config_loader/mod.rs | 11 +++ codex-rs/core/src/config_loader/tests.rs | 95 ++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index eba89fdb3d..838db0d5ff 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -25,6 +25,7 @@ use codex_protocol::config_types::TrustLevel; use codex_protocol::protocol::AskForApproval; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBufGuard; +use dunce::canonicalize as normalize_path; use serde::Deserialize; use std::io; use std::path::Path; @@ -236,6 +237,7 @@ pub async fn load_config_layers_state( &cwd, &project_trust_context.project_root, &project_trust_context, + codex_home, ) .await?; layers.extend(project_layers); @@ -671,7 +673,11 @@ async fn load_project_layers( cwd: &AbsolutePathBuf, project_root: &AbsolutePathBuf, trust_context: &ProjectTrustContext, + codex_home: &Path, ) -> io::Result> { + let codex_home_abs = AbsolutePathBuf::from_absolute_path(codex_home)?; + let codex_home_normalized = + normalize_path(codex_home_abs.as_path()).unwrap_or_else(|_| codex_home_abs.to_path_buf()); let mut dirs = cwd .as_path() .ancestors() @@ -702,6 +708,11 @@ async fn load_project_layers( let layer_dir = AbsolutePathBuf::from_absolute_path(dir)?; let decision = trust_context.decision_for_dir(&layer_dir); let dot_codex_abs = AbsolutePathBuf::from_absolute_path(&dot_codex)?; + let dot_codex_normalized = + normalize_path(dot_codex_abs.as_path()).unwrap_or_else(|_| dot_codex_abs.to_path_buf()); + if dot_codex_abs == codex_home_abs || dot_codex_normalized == codex_home_normalized { + continue; + } let config_file = dot_codex_abs.join(CONFIG_TOML_FILE)?; match tokio::fs::read_to_string(&config_file).await { Ok(contents) => { diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index 38d104fc19..355057f184 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -755,6 +755,101 @@ async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> s Ok(()) } +#[tokio::test] +async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::Result<()> { + let tmp = tempdir()?; + let home_dir = tmp.path().join("home"); + let codex_home = home_dir.join(".codex"); + tokio::fs::create_dir_all(&codex_home).await?; + tokio::fs::write(codex_home.join(CONFIG_TOML_FILE), "foo = \"user\"\n").await?; + + let cwd = AbsolutePathBuf::from_absolute_path(&home_dir)?; + let layers = load_config_layers_state( + &codex_home, + Some(cwd), + &[] as &[(String, TomlValue)], + LoaderOverrides::default(), + ) + .await?; + + let project_layers: Vec<_> = layers + .get_layers( + super::ConfigLayerStackOrdering::HighestPrecedenceFirst, + true, + ) + .into_iter() + .filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. })) + .collect(); + let expected: Vec<&ConfigLayerEntry> = Vec::new(); + assert_eq!(expected, project_layers); + assert_eq!( + layers.effective_config().get("foo"), + Some(&TomlValue::String("user".to_string())) + ); + + Ok(()) +} + +#[tokio::test] +async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Result<()> { + let tmp = tempdir()?; + let project_root = tmp.path().join("project"); + let nested = project_root.join("child"); + let project_dot_codex = project_root.join(".codex"); + let nested_dot_codex = nested.join(".codex"); + + tokio::fs::create_dir_all(&nested_dot_codex).await?; + tokio::fs::create_dir_all(project_root.join(".git")).await?; + tokio::fs::write(nested_dot_codex.join(CONFIG_TOML_FILE), "foo = \"child\"\n").await?; + + tokio::fs::create_dir_all(&project_dot_codex).await?; + make_config_for_test(&project_dot_codex, &project_root, TrustLevel::Trusted, None).await?; + let user_config_path = project_dot_codex.join(CONFIG_TOML_FILE); + let user_config_contents = tokio::fs::read_to_string(&user_config_path).await?; + tokio::fs::write( + &user_config_path, + format!("foo = \"user\"\n{user_config_contents}"), + ) + .await?; + + let cwd = AbsolutePathBuf::from_absolute_path(&nested)?; + let layers = load_config_layers_state( + &project_dot_codex, + Some(cwd), + &[] as &[(String, TomlValue)], + LoaderOverrides::default(), + ) + .await?; + + let project_layers: Vec<_> = layers + .get_layers( + super::ConfigLayerStackOrdering::HighestPrecedenceFirst, + true, + ) + .into_iter() + .filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. })) + .collect(); + + let child_config: TomlValue = toml::from_str("foo = \"child\"\n").expect("parse child config"); + assert_eq!( + vec![&ConfigLayerEntry { + name: super::ConfigLayerSource::Project { + dot_codex_folder: AbsolutePathBuf::from_absolute_path(&nested_dot_codex)?, + }, + config: child_config.clone(), + version: version_for_toml(&child_config), + disabled_reason: None, + }], + project_layers + ); + assert_eq!( + layers.effective_config().get("foo"), + Some(&TomlValue::String("child".to_string())) + ); + + Ok(()) +} + #[tokio::test] async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result<()> { let tmp = tempdir()?; From aacd530a41ef28552270e1ec2c1fb9e0676445ec Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 30 Jan 2026 12:57:19 -0800 Subject: [PATCH 019/139] Update copy (#10256) image --- codex-rs/tui/src/history_cell.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index aadfb318f8..c4bd766655 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -726,16 +726,17 @@ pub fn new_approval_decision_cell( ], ) } - ApprovedExecpolicyAmendment { .. } => { - let snippet = Span::from(exec_snippet(&command)).dim(); + ApprovedExecpolicyAmendment { + proposed_execpolicy_amendment, + } => { + let snippet = Span::from(exec_snippet(&proposed_execpolicy_amendment.command)).dim(); ( "✔ ".green(), vec![ "You ".into(), "approved".bold(), - " codex to run ".into(), + " codex to always run commands that start with ".into(), snippet, - " and applied the execpolicy amendment".bold(), ], ) } From 9008a0eff915042e46b1ecb2a1b32e3b2b5225a9 Mon Sep 17 00:00:00 2001 From: Skylar Graika Date: Fri, 30 Jan 2026 13:47:10 -0800 Subject: [PATCH 020/139] core: prevent shell_snapshot from inheriting stdin (#9735) Fixes #9559. When `shell_snapshot` runs, it may execute user startup files (e.g. `.bashrc`). If those files read from stdin (or if stdin is an interactive TTY under job control), the snapshot subprocess can block or receive `SIGTTIN` (as reported over SSH). This change explicitly sets `stdin` to `Stdio::null()` for the snapshot subprocess, so it can't read from the terminal. Regression test added that would hang/timeout without this change. Tests: `ulimit -n 4096 && cargo test -p codex-core`. cc @dongdongbh @etraut-openai --------- Co-authored-by: Skylar Graika --- codex-rs/core/src/shell_snapshot.rs | 90 +++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/codex-rs/core/src/shell_snapshot.rs b/codex-rs/core/src/shell_snapshot.rs index d6d738508a..7ba1b67caf 100644 --- a/codex-rs/core/src/shell_snapshot.rs +++ b/codex-rs/core/src/shell_snapshot.rs @@ -1,6 +1,7 @@ use std::io::ErrorKind; use std::path::Path; use std::path::PathBuf; +use std::process::Stdio; use std::sync::Arc; use std::time::Duration; use std::time::SystemTime; @@ -187,6 +188,7 @@ async fn run_script_with_timeout( // returns a ref of handler. let mut handler = Command::new(&args[0]); handler.args(&args[1..]); + handler.stdin(Stdio::null()); #[cfg(unix)] unsafe { handler.pre_exec(|| { @@ -477,6 +479,62 @@ mod tests { use tempfile::tempdir; + #[cfg(unix)] + struct BlockingStdinPipe { + original: i32, + write_end: i32, + } + + #[cfg(unix)] + impl BlockingStdinPipe { + fn install() -> Result { + let mut fds = [0i32; 2]; + if unsafe { libc::pipe(fds.as_mut_ptr()) } == -1 { + return Err(std::io::Error::last_os_error()).context("create stdin pipe"); + } + + let original = unsafe { libc::dup(libc::STDIN_FILENO) }; + if original == -1 { + let err = std::io::Error::last_os_error(); + unsafe { + libc::close(fds[0]); + libc::close(fds[1]); + } + return Err(err).context("dup stdin"); + } + + if unsafe { libc::dup2(fds[0], libc::STDIN_FILENO) } == -1 { + let err = std::io::Error::last_os_error(); + unsafe { + libc::close(fds[0]); + libc::close(fds[1]); + libc::close(original); + } + return Err(err).context("replace stdin"); + } + + unsafe { + libc::close(fds[0]); + } + + Ok(Self { + original, + write_end: fds[1], + }) + } + } + + #[cfg(unix)] + impl Drop for BlockingStdinPipe { + fn drop(&mut self) { + unsafe { + libc::dup2(self.original, libc::STDIN_FILENO); + libc::close(self.original); + libc::close(self.write_end); + } + } + } + #[cfg(not(target_os = "windows"))] fn assert_posix_snapshot_sections(snapshot: &str) { assert!(snapshot.contains("# Snapshot file")); @@ -557,6 +615,38 @@ mod tests { Ok(()) } + #[cfg(unix)] + #[tokio::test] + async fn snapshot_shell_does_not_inherit_stdin() -> Result<()> { + let _stdin_guard = BlockingStdinPipe::install()?; + + let dir = tempdir()?; + let home = dir.path(); + fs::write(home.join(".bashrc"), "read -r ignored\n").await?; + + let shell = Shell { + shell_type: ShellType::Bash, + shell_path: PathBuf::from("/bin/bash"), + shell_snapshot: crate::shell::empty_shell_snapshot_receiver(), + }; + + let home_display = home.display(); + let script = format!( + "HOME=\"{home_display}\"; export HOME; {}", + bash_snapshot_script() + ); + let output = run_script_with_timeout(&shell, &script, Duration::from_millis(500), true) + .await + .context("run snapshot command")?; + + assert!( + output.contains("# Snapshot file"), + "expected snapshot marker in output; output={output:?}" + ); + + Ok(()) + } + #[cfg(target_os = "linux")] #[tokio::test] async fn timed_out_snapshot_shell_is_terminated() -> Result<()> { From 5f81e8e70b3feeebb32a412331bc9fceb736821b Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 30 Jan 2026 13:54:05 -0800 Subject: [PATCH 021/139] Fix main (#10262) --- codex-rs/core/src/config_loader/tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index 355057f184..fd4898c2cd 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -769,6 +769,7 @@ async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::R Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), + None, ) .await?; @@ -818,6 +819,7 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), + None, ) .await?; From 0fac2744f744abfeabd71a18695a54f4f1006a2b Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 30 Jan 2026 14:12:50 -0800 Subject: [PATCH 022/139] Hide /approvals from the slash-command list (#10265) `/permissions` is the replacement. `/approvals` still available when typing. --- codex-rs/tui/src/bottom_pane/command_popup.rs | 9 ++++++--- codex-rs/tui/src/history_cell.rs | 5 ----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/command_popup.rs b/codex-rs/tui/src/bottom_pane/command_popup.rs index 48ee7d2d60..2ece7b4fde 100644 --- a/codex-rs/tui/src/bottom_pane/command_popup.rs +++ b/codex-rs/tui/src/bottom_pane/command_popup.rs @@ -14,6 +14,11 @@ use codex_protocol::custom_prompts::CustomPrompt; use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX; use std::collections::HashSet; +// Hide alias commands in the default popup list so each unique action appears once. +// `quit` is an alias of `exit`, so we skip `quit` here. +// `approvals` is an alias of `permissions`. +const ALIAS_COMMANDS: &[SlashCommand] = &[SlashCommand::Quit, SlashCommand::Approvals]; + /// A selectable item in the popup: either a built-in command or a user prompt. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum CommandItem { @@ -122,9 +127,7 @@ impl CommandPopup { if filter.is_empty() { // Built-ins first, in presentation order. for (_, cmd) in self.builtins.iter() { - // Hide alias commands in the default popup list so each unique action appears once. - // `quit` is an alias of `exit`, so we skip `quit` here. - if *cmd == SlashCommand::Quit { + if ALIAS_COMMANDS.contains(cmd) { continue; } out.push((CommandItem::Builtin(*cmd), None)); diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index c4bd766655..39482bc1f3 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -974,11 +974,6 @@ pub(crate) fn new_session_info( "/status".into(), " - show current session configuration".dim(), ]), - Line::from(vec![ - " ".into(), - "/approvals".into(), - " - choose what Codex can do without approval".dim(), - ]), Line::from(vec![ " ".into(), "/permissions".into(), From 748141bdda83a5dac6c39ab37b49b2559cb5c2bf Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 30 Jan 2026 14:14:29 -0800 Subject: [PATCH 023/139] Update announcement_tip.toml (#10267) Extend the test for dev version --- announcement_tip.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/announcement_tip.toml b/announcement_tip.toml index 0070003219..b2cda2f044 100644 --- a/announcement_tip.toml +++ b/announcement_tip.toml @@ -14,4 +14,4 @@ target_app = "cli" [[announcements]] content = "This is a test announcement" version_regex = "^0\\.0\\.0$" -to_date = "2026-01-10" +to_date = "2026-05-10" From d59685f6d4f882223f29c6b565363b1363c2f4f1 Mon Sep 17 00:00:00 2001 From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:20:23 -0800 Subject: [PATCH 024/139] file-search: multi-root walk (#10240) Instead of a separate walker for each root in a multi-root walk, use a single walker. --- codex-rs/app-server/src/fuzzy_file_search.rs | 93 ++++---- codex-rs/core/src/rollout/list.rs | 33 +-- codex-rs/file-search/src/lib.rs | 210 ++++++++++-------- codex-rs/file-search/src/main.rs | 4 +- codex-rs/tui/src/bottom_pane/chat_composer.rs | 2 +- .../tui/src/bottom_pane/file_search_popup.rs | 8 +- codex-rs/tui/src/file_search.rs | 11 +- 7 files changed, 172 insertions(+), 189 deletions(-) diff --git a/codex-rs/app-server/src/fuzzy_file_search.rs b/codex-rs/app-server/src/fuzzy_file_search.rs index eb3dfe00bf..fde3031813 100644 --- a/codex-rs/app-server/src/fuzzy_file_search.rs +++ b/codex-rs/app-server/src/fuzzy_file_search.rs @@ -1,17 +1,14 @@ use std::num::NonZero; -use std::num::NonZeroUsize; use std::path::PathBuf; use std::sync::Arc; use std::sync::atomic::AtomicBool; use codex_app_server_protocol::FuzzyFileSearchResult; use codex_file_search as file_search; -use tokio::task::JoinSet; use tracing::warn; -const LIMIT_PER_ROOT: usize = 50; +const MATCH_LIMIT: usize = 50; const MAX_THREADS: usize = 12; -const COMPUTE_INDICES: bool = true; pub(crate) async fn run_fuzzy_file_search( query: String, @@ -23,64 +20,54 @@ pub(crate) async fn run_fuzzy_file_search( } #[expect(clippy::expect_used)] - let limit_per_root = - NonZero::new(LIMIT_PER_ROOT).expect("LIMIT_PER_ROOT should be a valid non-zero usize"); + let limit = NonZero::new(MATCH_LIMIT).expect("MATCH_LIMIT should be a valid non-zero usize"); let cores = std::thread::available_parallelism() .map(std::num::NonZero::get) .unwrap_or(1); let threads = cores.min(MAX_THREADS); - let threads_per_root = (threads / roots.len()).max(1); - let threads = NonZero::new(threads_per_root).unwrap_or(NonZeroUsize::MIN); + #[expect(clippy::expect_used)] + let threads = NonZero::new(threads.max(1)).expect("threads should be non-zero"); + let search_dirs: Vec = roots.iter().map(PathBuf::from).collect(); - let mut files: Vec = Vec::new(); - let mut join_set = JoinSet::new(); - - for root in roots { - let search_dir = PathBuf::from(&root); - let query = query.clone(); - let cancel_flag = cancellation_flag.clone(); - join_set.spawn_blocking(move || { - match file_search::run( - query.as_str(), - limit_per_root, - &search_dir, - Vec::new(), + let mut files = match tokio::task::spawn_blocking(move || { + file_search::run( + query.as_str(), + search_dirs, + file_search::FileSearchOptions { + limit, threads, - cancel_flag, - COMPUTE_INDICES, - true, - ) { - Ok(res) => Ok((root, res)), - Err(err) => Err((root, err)), - } - }); - } - - while let Some(res) = join_set.join_next().await { - match res { - Ok(Ok((root, res))) => { - for m in res.matches { - let path = m.path; - let file_name = file_search::file_name_from_path(&path); - let result = FuzzyFileSearchResult { - root: root.clone(), - path, - file_name, - score: m.score, - indices: m.indices, - }; - files.push(result); + compute_indices: true, + ..Default::default() + }, + Some(cancellation_flag), + ) + }) + .await + { + Ok(Ok(res)) => res + .matches + .into_iter() + .map(|m| { + let file_name = m.path.file_name().unwrap_or_default(); + FuzzyFileSearchResult { + root: m.root.to_string_lossy().to_string(), + path: m.path.to_string_lossy().to_string(), + file_name: file_name.to_string_lossy().to_string(), + score: m.score, + indices: m.indices, } - } - Ok(Err((root, err))) => { - warn!("fuzzy-file-search in dir '{root}' failed: {err}"); - } - Err(err) => { - warn!("fuzzy-file-search join_next failed: {err}"); - } + }) + .collect::>(), + Ok(Err(err)) => { + warn!("fuzzy-file-search failed: {err}"); + Vec::new() } - } + Err(err) => { + warn!("fuzzy-file-search join failed: {err}"); + Vec::new() + } + }; files.sort_by(file_search::cmp_by_score_desc_then_path_asc::< FuzzyFileSearchResult, diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index edff86606b..18e862c7f0 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -6,8 +6,6 @@ use std::num::NonZero; use std::ops::ControlFlow; use std::path::Path; use std::path::PathBuf; -use std::sync::Arc; -use std::sync::atomic::AtomicBool; use time::OffsetDateTime; use time::PrimitiveDateTime; use time::format_description::FormatItem; @@ -1075,30 +1073,17 @@ async fn find_thread_path_by_id_str_in_subdir( // This is safe because we know the values are valid. #[allow(clippy::unwrap_used)] let limit = NonZero::new(1).unwrap(); - // This is safe because we know the values are valid. - #[allow(clippy::unwrap_used)] - let threads = NonZero::new(2).unwrap(); - let cancel = Arc::new(AtomicBool::new(false)); - let exclude: Vec = Vec::new(); - let compute_indices = false; - - let results = file_search::run( - id_str, + let options = file_search::FileSearchOptions { limit, - &root, - exclude, - threads, - cancel, - compute_indices, - false, - ) - .map_err(|e| io::Error::other(format!("file search failed: {e}")))?; + compute_indices: false, + respect_gitignore: false, + ..Default::default() + }; - let found = results - .matches - .into_iter() - .next() - .map(|m| root.join(m.path)); + let results = file_search::run(id_str, vec![root], options, None) + .map_err(|e| io::Error::other(format!("file search failed: {e}")))?; + + let found = results.matches.into_iter().next().map(|m| m.full_path()); // Checking if DB is at parity. // TODO(jif): sqlite migration phase 1 diff --git a/codex-rs/file-search/src/lib.rs b/codex-rs/file-search/src/lib.rs index 39255fa36e..70cb583ebe 100644 --- a/codex-rs/file-search/src/lib.rs +++ b/codex-rs/file-search/src/lib.rs @@ -44,18 +44,25 @@ pub use cli::Cli; /// * `path` – Path to the matched file (relative to the search directory). /// * `indices` – Optional list of character indices that matched the query. /// These are only filled when the caller of [`run`] sets -/// `compute_indices` to `true`. The indices vector follows the +/// `options.compute_indices` to `true`. The indices vector follows the /// guidance from `nucleo::pattern::Pattern::indices`: they are /// unique and sorted in ascending order so that callers can use /// them directly for highlighting. #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct FileMatch { pub score: u32, - pub path: String, + pub path: PathBuf, + pub root: PathBuf, #[serde(skip_serializing_if = "Option::is_none")] pub indices: Option>, // Sorted & deduplicated when present } +impl FileMatch { + pub fn full_path(&self) -> PathBuf { + self.root.join(&self.path) + } +} + /// Returns the final path component for a matched path, falling back to the full path. pub fn file_name_from_path(path: &str) -> String { Path::new(path) @@ -80,7 +87,7 @@ pub struct FileSearchSnapshot { } #[derive(Debug, Clone)] -pub struct SessionOptions { +pub struct FileSearchOptions { pub limit: NonZero, pub exclude: Vec, pub threads: NonZero, @@ -88,7 +95,7 @@ pub struct SessionOptions { pub respect_gitignore: bool, } -impl Default for SessionOptions { +impl Default for FileSearchOptions { fn default() -> Self { Self { #[expect(clippy::unwrap_used)] @@ -133,19 +140,24 @@ impl Drop for FileSearchSession { pub fn create_session( search_directory: &Path, - options: SessionOptions, + options: FileSearchOptions, reporter: Arc, ) -> anyhow::Result { - create_session_inner(search_directory, options, reporter, None) + create_session_inner( + vec![search_directory.to_path_buf()], + options, + reporter, + None, + ) } fn create_session_inner( - search_directory: &Path, - options: SessionOptions, + search_directories: Vec, + options: FileSearchOptions, reporter: Arc, cancel_flag: Option>, ) -> anyhow::Result { - let SessionOptions { + let FileSearchOptions { limit, exclude, threads, @@ -153,7 +165,10 @@ fn create_session_inner( respect_gitignore, } = options; - let override_matcher = build_override_matcher(search_directory, &exclude)?; + let Some(primary_search_directory) = search_directories.first() else { + anyhow::bail!("at least one search directory is required"); + }; + let override_matcher = build_override_matcher(primary_search_directory, &exclude)?; let (work_tx, work_rx) = unbounded(); let notify_tx = work_tx.clone(); @@ -171,7 +186,7 @@ fn create_session_inner( let cancelled = cancel_flag.unwrap_or_else(|| Arc::new(AtomicBool::new(false))); let inner = Arc::new(SessionInner { - search_directory: search_directory.to_path_buf(), + search_directories, limit: limit.get(), threads: threads.get(), compute_indices, @@ -239,19 +254,20 @@ pub async fn run_main( } }; - let cancel_flag = Arc::new(AtomicBool::new(false)); let FileSearchResults { total_match_count, matches, } = run( &pattern_text, - limit, - &search_directory, - exclude, - threads, - cancel_flag, - compute_indices, - true, + vec![search_directory.to_path_buf()], + FileSearchOptions { + limit, + exclude, + threads, + compute_indices, + respect_gitignore: true, + }, + None, )?; let match_count = matches.len(); let matches_truncated = total_match_count > match_count; @@ -268,30 +284,14 @@ pub async fn run_main( /// The worker threads will periodically check `cancel_flag` to see if they /// should stop processing files. -#[allow(clippy::too_many_arguments)] pub fn run( pattern_text: &str, - limit: NonZero, - search_directory: &Path, - exclude: Vec, - threads: NonZero, - cancel_flag: Arc, - compute_indices: bool, - respect_gitignore: bool, + roots: Vec, + options: FileSearchOptions, + cancel_flag: Option>, ) -> anyhow::Result { let reporter = Arc::new(RunReporter::default()); - let session = create_session_inner( - search_directory, - SessionOptions { - limit, - exclude, - threads, - compute_indices, - respect_gitignore, - }, - reporter.clone(), - Some(cancel_flag), - )?; + let session = create_session_inner(roots, options, reporter.clone(), cancel_flag)?; session.update_query(pattern_text); @@ -339,7 +339,7 @@ fn create_pattern(pattern: &str) -> Pattern { } struct SessionInner { - search_directory: PathBuf, + search_directories: Vec, limit: usize, threads: usize, compute_indices: bool, @@ -373,12 +373,39 @@ fn build_override_matcher( Ok(Some(matcher)) } +fn get_file_path<'a>(path: &'a Path, search_directories: &[PathBuf]) -> Option<(usize, &'a str)> { + let mut best_match: Option<(usize, &Path)> = None; + for (idx, root) in search_directories.iter().enumerate() { + if let Ok(rel_path) = path.strip_prefix(root) { + let root_depth = root.components().count(); + match best_match { + Some((best_idx, _)) + if search_directories[best_idx].components().count() >= root_depth => {} + _ => { + best_match = Some((idx, rel_path)); + } + } + } + } + + let (root_idx, rel_path) = best_match?; + rel_path.to_str().map(|p| (root_idx, p)) +} + fn walker_worker( inner: Arc, override_matcher: Option, injector: Injector>, ) { - let mut walk_builder = WalkBuilder::new(&inner.search_directory); + let Some(first_root) = inner.search_directories.first() else { + let _ = inner.work_tx.send(WorkSignal::WalkComplete); + return; + }; + + let mut walk_builder = WalkBuilder::new(first_root); + for root in inner.search_directories.iter().skip(1) { + walk_builder.add(root); + } walk_builder .threads(inner.threads) // Allow hidden entries. @@ -401,36 +428,29 @@ fn walker_worker( let walker = walk_builder.build_parallel(); - fn get_file_path<'a>( - entry_result: &'a Result, - search_directory: &Path, - ) -> Option<&'a str> { - let entry = match entry_result { - Ok(entry) => entry, - Err(_) => return None, - }; - if entry.file_type().is_some_and(|ft| ft.is_dir()) { - return None; - } - let path = entry.path(); - match path.strip_prefix(search_directory) { - Ok(rel_path) => rel_path.to_str(), - Err(_) => None, - } - } - walker.run(|| { const CHECK_INTERVAL: usize = 1024; let mut n = 0; - let search_directory = inner.search_directory.clone(); + let search_directories = inner.search_directories.clone(); let injector = injector.clone(); let cancelled = inner.cancelled.clone(); let shutdown = inner.shutdown.clone(); Box::new(move |entry| { - if let Some(path) = get_file_path(&entry, &search_directory) { - injector.push(Arc::from(path), |path, cols| { - cols[0] = Utf32String::from(path.as_ref()); + let entry = match entry { + Ok(entry) => entry, + Err(_) => return ignore::WalkState::Continue, + }; + if entry.file_type().is_some_and(|ft| ft.is_dir()) { + return ignore::WalkState::Continue; + } + let path = entry.path(); + let Some(full_path) = path.to_str() else { + return ignore::WalkState::Continue; + }; + if let Some((_, relative_path)) = get_file_path(path, &search_directories) { + injector.push(Arc::from(full_path), |_, cols| { + cols[0] = Utf32String::from(relative_path); }); } n += 1; @@ -513,6 +533,8 @@ fn matcher_worker( .take(limit) .filter_map(|match_| { let item = snapshot.get_item(match_.idx)?; + let full_path = item.data.as_ref(); + let (root_idx, relative_path) = get_file_path(Path::new(full_path), &inner.search_directories)?; let indices = if let Some(indices_matcher) = indices_matcher.as_mut() { let mut idx_vec = Vec::::new(); let haystack = item.matcher_columns[0].slice(..); @@ -525,7 +547,8 @@ fn matcher_worker( }; Some(FileMatch { score: match_.score, - path: item.data.as_ref().to_string(), + path: PathBuf::from(relative_path), + root: inner.search_directories[root_idx].clone(), indices, }) }) @@ -723,7 +746,7 @@ mod tests { fn session_scanned_file_count_is_monotonic_across_queries() { let dir = create_temp_tree(200); let reporter = Arc::new(RecordingReporter::default()); - let session = create_session(dir.path(), SessionOptions::default(), reporter.clone()) + let session = create_session(dir.path(), FileSearchOptions::default(), reporter.clone()) .expect("session"); session.update_query("file-00"); @@ -743,7 +766,7 @@ mod tests { fn session_streams_updates_before_walk_complete() { let dir = create_temp_tree(600); let reporter = Arc::new(RecordingReporter::default()); - let session = create_session(dir.path(), SessionOptions::default(), reporter.clone()) + let session = create_session(dir.path(), FileSearchOptions::default(), reporter.clone()) .expect("session"); session.update_query("file-0"); @@ -760,7 +783,7 @@ mod tests { fs::write(dir.path().join("alpha.txt"), "alpha").unwrap(); fs::write(dir.path().join("beta.txt"), "beta").unwrap(); let reporter = Arc::new(RecordingReporter::default()); - let session = create_session(dir.path(), SessionOptions::default(), reporter.clone()) + let session = create_session(dir.path(), FileSearchOptions::default(), reporter.clone()) .expect("session"); session.update_query("alpha"); @@ -776,7 +799,7 @@ mod tests { last_update .matches .iter() - .any(|file_match| file_match.path.contains("beta.txt")) + .any(|file_match| file_match.path.to_string_lossy().contains("beta.txt")) ); } @@ -787,8 +810,8 @@ mod tests { fs::write(dir.path().join("beta.txt"), "beta").unwrap(); let reporter = Arc::new(RecordingReporter::default()); let session = create_session_inner( - dir.path(), - SessionOptions::default(), + vec![dir.path().to_path_buf()], + FileSearchOptions::default(), reporter.clone(), None, ) @@ -816,8 +839,8 @@ mod tests { let reporter_a = Arc::new(RecordingReporter::default()); let session_a = create_session_inner( - root_a.path(), - SessionOptions::default(), + vec![root_a.path().to_path_buf()], + FileSearchOptions::default(), reporter_a, Some(cancel_flag.clone()), ) @@ -825,8 +848,8 @@ mod tests { let reporter_b = Arc::new(RecordingReporter::default()); let session_b = create_session_inner( - root_b.path(), - SessionOptions::default(), + vec![root_b.path().to_path_buf()], + FileSearchOptions::default(), reporter_b.clone(), Some(cancel_flag), ) @@ -846,7 +869,7 @@ mod tests { fn session_emits_updates_when_query_changes() { let dir = create_temp_tree(200); let reporter = Arc::new(RecordingReporter::default()); - let session = create_session(dir.path(), SessionOptions::default(), reporter.clone()) + let session = create_session(dir.path(), FileSearchOptions::default(), reporter.clone()) .expect("session"); session.update_query("zzzzzzzz"); @@ -866,17 +889,15 @@ mod tests { #[test] fn run_returns_matches_for_query() { let dir = create_temp_tree(40); - let results = run( - "file-000", - NonZero::new(20).unwrap(), - dir.path(), - Vec::new(), - NonZero::new(2).unwrap(), - Arc::new(AtomicBool::new(false)), - false, - true, - ) - .expect("run ok"); + let options = FileSearchOptions { + limit: NonZero::new(20).unwrap(), + exclude: Vec::new(), + threads: NonZero::new(2).unwrap(), + compute_indices: false, + respect_gitignore: true, + }; + let results = + run("file-000", vec![dir.path().to_path_buf()], options, None).expect("run ok"); assert!(!results.matches.is_empty()); assert!(results.total_match_count >= results.matches.len()); @@ -884,7 +905,7 @@ mod tests { results .matches .iter() - .any(|m| m.path.contains("file-0000.txt")) + .any(|m| m.path.to_string_lossy().contains("file-0000.txt")) ); } @@ -893,19 +914,14 @@ mod tests { let dir = create_temp_tree(200); let cancel_flag = Arc::new(AtomicBool::new(true)); let search_dir = dir.path().to_path_buf(); + let options = FileSearchOptions { + compute_indices: false, + ..Default::default() + }; let (tx, rx) = std::sync::mpsc::channel(); let handle = thread::spawn(move || { - let result = run( - "file-", - NonZero::new(20).unwrap(), - &search_dir, - Vec::new(), - NonZero::new(2).unwrap(), - cancel_flag, - false, - true, - ); + let result = run("file-", vec![search_dir], options, Some(cancel_flag)); let _ = tx.send(result); }); diff --git a/codex-rs/file-search/src/main.rs b/codex-rs/file-search/src/main.rs index ef39174df5..4715d1bd62 100644 --- a/codex-rs/file-search/src/main.rs +++ b/codex-rs/file-search/src/main.rs @@ -39,7 +39,7 @@ impl Reporter for StdioReporter { // iterating over the characters. let mut indices_iter = indices.iter().peekable(); - for (i, c) in file_match.path.chars().enumerate() { + for (i, c) in file_match.path.to_string_lossy().chars().enumerate() { match indices_iter.peek() { Some(next) if **next == i as u32 => { // ANSI escape code for bold: \x1b[1m ... \x1b[0m @@ -54,7 +54,7 @@ impl Reporter for StdioReporter { } println!(); } else { - println!("{}", file_match.path); + println!("{}", file_match.path.to_string_lossy()); } } diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 1d3cf942fb..979213112f 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -1306,7 +1306,7 @@ impl ChatComposer { return (InputResult::None, true); }; - let sel_path = sel.to_string(); + let sel_path = sel.to_string_lossy().to_string(); // If selected path looks like an image (png/jpeg), attach as image instead of inserting text. let is_image = Self::is_image_path(&sel_path); if is_image { diff --git a/codex-rs/tui/src/bottom_pane/file_search_popup.rs b/codex-rs/tui/src/bottom_pane/file_search_popup.rs index e018c7ff8a..3e6ef814a2 100644 --- a/codex-rs/tui/src/bottom_pane/file_search_popup.rs +++ b/codex-rs/tui/src/bottom_pane/file_search_popup.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use codex_file_search::FileMatch; use ratatui::buffer::Buffer; use ratatui::layout::Rect; @@ -89,11 +91,11 @@ impl FileSearchPopup { self.state.ensure_visible(len, len.min(MAX_POPUP_ROWS)); } - pub(crate) fn selected_match(&self) -> Option<&str> { + pub(crate) fn selected_match(&self) -> Option<&PathBuf> { self.state .selected_idx .and_then(|idx| self.matches.get(idx)) - .map(|file_match| file_match.path.as_str()) + .map(|file_match| &file_match.path) } pub(crate) fn calculate_required_height(&self) -> u16 { @@ -116,7 +118,7 @@ impl WidgetRef for &FileSearchPopup { self.matches .iter() .map(|m| GenericDisplayRow { - name: m.path.clone(), + name: m.path.to_string_lossy().to_string(), match_indices: m .indices .as_ref() diff --git a/codex-rs/tui/src/file_search.rs b/codex-rs/tui/src/file_search.rs index 88c56f2316..ff55ac1274 100644 --- a/codex-rs/tui/src/file_search.rs +++ b/codex-rs/tui/src/file_search.rs @@ -6,7 +6,6 @@ //! on every keystroke, and drops the session when the query becomes empty. use codex_file_search as file_search; -use std::num::NonZeroUsize; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; @@ -14,9 +13,6 @@ use std::sync::Mutex; use crate::app_event::AppEvent; use crate::app_event_sender::AppEventSender; -const MAX_FILE_SEARCH_RESULTS: NonZeroUsize = NonZeroUsize::new(20).unwrap(); -const NUM_FILE_SEARCH_THREADS: NonZeroUsize = NonZeroUsize::new(2).unwrap(); - pub(crate) struct FileSearchManager { state: Arc>, search_dir: PathBuf, @@ -75,12 +71,9 @@ impl FileSearchManager { }); let session = file_search::create_session( &self.search_dir, - file_search::SessionOptions { - limit: MAX_FILE_SEARCH_RESULTS, - exclude: Vec::new(), - threads: NUM_FILE_SEARCH_THREADS, + file_search::FileSearchOptions { compute_indices: true, - respect_gitignore: true, + ..Default::default() }, reporter, ); From 31d1e493400787200f1d7cfd929cc26b1730236d Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Fri, 30 Jan 2026 14:52:37 -0800 Subject: [PATCH 025/139] fix: dont auto-enable web_search for azure (#10266) seeing issues with azure after default-enabling web search: #10071, #10257. need to work with azure to fix api-side, for now turning off default-enable of web_search for azure. diff is big because i moved logic to reuse --- codex-rs/codex-api/src/lib.rs | 1 + codex-rs/codex-api/src/provider.rs | 80 +++++++++++++--- codex-rs/core/src/codex.rs | 1 + codex-rs/core/src/config/mod.rs | 16 +++- codex-rs/core/src/model_provider_info.rs | 93 ++----------------- codex-rs/core/tests/suite/mod.rs | 2 +- .../{web_search_cached.rs => web_search.rs} | 53 +++++++++++ 7 files changed, 148 insertions(+), 98 deletions(-) rename codex-rs/core/tests/suite/{web_search_cached.rs => web_search.rs} (76%) diff --git a/codex-rs/codex-api/src/lib.rs b/codex-rs/codex-api/src/lib.rs index 0f608fd239..89e5c4dc5f 100644 --- a/codex-rs/codex-api/src/lib.rs +++ b/codex-rs/codex-api/src/lib.rs @@ -33,6 +33,7 @@ pub use crate::endpoint::responses_websocket::ResponsesWebsocketConnection; pub use crate::error::ApiError; pub use crate::provider::Provider; pub use crate::provider::WireApi; +pub use crate::provider::is_azure_responses_wire_base_url; pub use crate::requests::ChatRequest; pub use crate::requests::ChatRequestBuilder; pub use crate::requests::ResponsesRequest; diff --git a/codex-rs/codex-api/src/provider.rs b/codex-rs/codex-api/src/provider.rs index 8fba2905bf..d7e7c19416 100644 --- a/codex-rs/codex-api/src/provider.rs +++ b/codex-rs/codex-api/src/provider.rs @@ -95,16 +95,7 @@ impl Provider { } pub fn is_azure_responses_endpoint(&self) -> bool { - if self.wire != WireApi::Responses { - return false; - } - - if self.name.eq_ignore_ascii_case("azure") { - return true; - } - - self.base_url.to_ascii_lowercase().contains("openai.azure.") - || matches_azure_responses_base_url(&self.base_url) + is_azure_responses_wire_base_url(self.wire.clone(), &self.name, Some(&self.base_url)) } pub fn websocket_url_for_path(&self, path: &str) -> Result { @@ -121,6 +112,23 @@ impl Provider { } } +pub fn is_azure_responses_wire_base_url(wire: WireApi, name: &str, base_url: Option<&str>) -> bool { + if wire != WireApi::Responses { + return false; + } + + if name.eq_ignore_ascii_case("azure") { + return true; + } + + let Some(base_url) = base_url else { + return false; + }; + + let base = base_url.to_ascii_lowercase(); + base.contains("openai.azure.") || matches_azure_responses_base_url(&base) +} + fn matches_azure_responses_base_url(base_url: &str) -> bool { const AZURE_MARKERS: [&str; 5] = [ "cognitiveservices.azure.", @@ -129,6 +137,54 @@ fn matches_azure_responses_base_url(base_url: &str) -> bool { "azurefd.", "windows.net/openai", ]; - let base = base_url.to_ascii_lowercase(); - AZURE_MARKERS.iter().any(|marker| base.contains(marker)) + AZURE_MARKERS.iter().any(|marker| base_url.contains(marker)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn detects_azure_responses_base_urls() { + let positive_cases = [ + "https://foo.openai.azure.com/openai", + "https://foo.openai.azure.us/openai/deployments/bar", + "https://foo.cognitiveservices.azure.cn/openai", + "https://foo.aoai.azure.com/openai", + "https://foo.openai.azure-api.net/openai", + "https://foo.z01.azurefd.net/", + ]; + + for base_url in positive_cases { + assert!( + is_azure_responses_wire_base_url(WireApi::Responses, "test", Some(base_url)), + "expected {base_url} to be detected as Azure" + ); + } + + assert!(is_azure_responses_wire_base_url( + WireApi::Responses, + "Azure", + Some("https://example.com") + )); + + let negative_cases = [ + "https://api.openai.com/v1", + "https://example.com/openai", + "https://myproxy.azurewebsites.net/openai", + ]; + + for base_url in negative_cases { + assert!( + !is_azure_responses_wire_base_url(WireApi::Responses, "test", Some(base_url)), + "expected {base_url} not to be detected as Azure" + ); + } + + assert!(!is_azure_responses_wire_base_url( + WireApi::Chat, + "Azure", + Some("https://foo.openai.azure.com/openai") + )); + } } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 5a1ce90dd9..64d79d392f 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -635,6 +635,7 @@ impl Session { per_turn_config.model_personality = session_configuration.personality; per_turn_config.web_search_mode = Some(resolve_web_search_mode_for_turn( per_turn_config.web_search_mode, + session_configuration.provider.is_azure_responses_endpoint(), session_configuration.sandbox_policy.get(), )); per_turn_config.features = config.features.clone(); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 2e1c853620..bb84cd6755 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1246,11 +1246,15 @@ fn resolve_web_search_mode( pub(crate) fn resolve_web_search_mode_for_turn( explicit_mode: Option, + is_azure_responses_endpoint: bool, sandbox_policy: &SandboxPolicy, ) -> WebSearchMode { if let Some(mode) = explicit_mode { return mode; } + if is_azure_responses_endpoint { + return WebSearchMode::Disabled; + } if matches!(sandbox_policy, SandboxPolicy::DangerFullAccess) { WebSearchMode::Live } else { @@ -2347,14 +2351,14 @@ trust_level = "trusted" #[test] fn web_search_mode_for_turn_defaults_to_cached_when_unset() { - let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::ReadOnly); + let mode = resolve_web_search_mode_for_turn(None, false, &SandboxPolicy::ReadOnly); assert_eq!(mode, WebSearchMode::Cached); } #[test] fn web_search_mode_for_turn_defaults_to_live_for_danger_full_access() { - let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::DangerFullAccess); + let mode = resolve_web_search_mode_for_turn(None, false, &SandboxPolicy::DangerFullAccess); assert_eq!(mode, WebSearchMode::Live); } @@ -2363,12 +2367,20 @@ trust_level = "trusted" fn web_search_mode_for_turn_prefers_explicit_value() { let mode = resolve_web_search_mode_for_turn( Some(WebSearchMode::Cached), + false, &SandboxPolicy::DangerFullAccess, ); assert_eq!(mode, WebSearchMode::Cached); } + #[test] + fn web_search_mode_for_turn_disables_for_azure_responses_endpoint() { + let mode = resolve_web_search_mode_for_turn(None, true, &SandboxPolicy::DangerFullAccess); + + assert_eq!(mode, WebSearchMode::Disabled); + } + #[test] fn profile_legacy_toggles_override_base() -> std::io::Result<()> { let codex_home = TempDir::new()?; diff --git a/codex-rs/core/src/model_provider_info.rs b/codex-rs/core/src/model_provider_info.rs index 56ff76d7f6..6ba913489e 100644 --- a/codex-rs/core/src/model_provider_info.rs +++ b/codex-rs/core/src/model_provider_info.rs @@ -9,6 +9,7 @@ use crate::auth::AuthMode; use crate::error::EnvVarError; use codex_api::Provider as ApiProvider; use codex_api::WireApi as ApiWireApi; +use codex_api::is_azure_responses_wire_base_url; use codex_api::provider::RetryConfig as ApiRetryConfig; use http::HeaderMap; use http::header::HeaderName; @@ -170,6 +171,15 @@ impl ModelProviderInfo { }) } + pub(crate) fn is_azure_responses_endpoint(&self) -> bool { + let wire = match self.wire_api { + WireApi::Responses => ApiWireApi::Responses, + WireApi::Chat => ApiWireApi::Chat, + }; + + is_azure_responses_wire_base_url(wire, &self.name, self.base_url.as_deref()) + } + /// If `env_key` is Some, returns the API key for this provider if present /// (and non-empty) in the environment. If `env_key` is required but /// cannot be found, returns an error. @@ -432,87 +442,4 @@ env_http_headers = { "X-Example-Env-Header" = "EXAMPLE_ENV_VAR" } let provider: ModelProviderInfo = toml::from_str(azure_provider_toml).unwrap(); assert_eq!(expected_provider, provider); } - - #[test] - fn detects_azure_responses_base_urls() { - let positive_cases = [ - "https://foo.openai.azure.com/openai", - "https://foo.openai.azure.us/openai/deployments/bar", - "https://foo.cognitiveservices.azure.cn/openai", - "https://foo.aoai.azure.com/openai", - "https://foo.openai.azure-api.net/openai", - "https://foo.z01.azurefd.net/", - ]; - for base_url in positive_cases { - let provider = ModelProviderInfo { - name: "test".into(), - base_url: Some(base_url.into()), - env_key: None, - env_key_instructions: None, - experimental_bearer_token: None, - wire_api: WireApi::Responses, - query_params: None, - http_headers: None, - env_http_headers: None, - request_max_retries: None, - stream_max_retries: None, - stream_idle_timeout_ms: None, - requires_openai_auth: false, - supports_websockets: false, - }; - let api = provider.to_api_provider(None).expect("api provider"); - assert!( - api.is_azure_responses_endpoint(), - "expected {base_url} to be detected as Azure" - ); - } - - let named_provider = ModelProviderInfo { - name: "Azure".into(), - base_url: Some("https://example.com".into()), - env_key: None, - env_key_instructions: None, - experimental_bearer_token: None, - wire_api: WireApi::Responses, - query_params: None, - http_headers: None, - env_http_headers: None, - request_max_retries: None, - stream_max_retries: None, - stream_idle_timeout_ms: None, - requires_openai_auth: false, - supports_websockets: false, - }; - let named_api = named_provider.to_api_provider(None).expect("api provider"); - assert!(named_api.is_azure_responses_endpoint()); - - let negative_cases = [ - "https://api.openai.com/v1", - "https://example.com/openai", - "https://myproxy.azurewebsites.net/openai", - ]; - for base_url in negative_cases { - let provider = ModelProviderInfo { - name: "test".into(), - base_url: Some(base_url.into()), - env_key: None, - env_key_instructions: None, - experimental_bearer_token: None, - wire_api: WireApi::Responses, - query_params: None, - http_headers: None, - env_http_headers: None, - request_max_retries: None, - stream_max_retries: None, - stream_idle_timeout_ms: None, - requires_openai_auth: false, - supports_websockets: false, - }; - let api = provider.to_api_provider(None).expect("api provider"); - assert!( - !api.is_azure_responses_endpoint(), - "expected {base_url} not to be detected as Azure" - ); - } - } } diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index defc704353..0c3f5bf1a2 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -80,5 +80,5 @@ mod unstable_features_warning; mod user_notification; mod user_shell_cmd; mod view_image; -mod web_search_cached; +mod web_search; mod websocket_fallback; diff --git a/codex-rs/core/tests/suite/web_search_cached.rs b/codex-rs/core/tests/suite/web_search.rs similarity index 76% rename from codex-rs/core/tests/suite/web_search_cached.rs rename to codex-rs/core/tests/suite/web_search.rs index df8b7dbb5b..9397e88edb 100644 --- a/codex-rs/core/tests/suite/web_search_cached.rs +++ b/codex-rs/core/tests/suite/web_search.rs @@ -1,5 +1,7 @@ #![allow(clippy::unwrap_used)] +use codex_core::WireApi; +use codex_core::built_in_model_providers; use codex_core::features::Feature; use codex_core::protocol::SandboxPolicy; use codex_protocol::config_types::WebSearchMode; @@ -25,6 +27,15 @@ fn find_web_search_tool(body: &Value) -> &Value { .expect("tools should include a web_search tool") } +#[allow(clippy::expect_used)] +fn has_web_search_tool(body: &Value) -> bool { + body["tools"] + .as_array() + .expect("request body should include tools array") + .iter() + .any(|tool| tool.get("type").and_then(Value::as_str) == Some("web_search")) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn web_search_mode_cached_sets_external_web_access_false() { skip_if_no_network!(); @@ -174,3 +185,45 @@ async fn web_search_mode_updates_between_turns_with_sandbox_policy() { "danger-full-access policy should default web_search to live" ); } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn web_search_mode_defaults_to_disabled_for_azure_responses() { + skip_if_no_network!(); + + let server = start_mock_server().await; + let sse = sse_completed("resp-1"); + let resp_mock = responses::mount_sse_once(&server, sse).await; + + let mut builder = test_codex() + .with_model("gpt-5-codex") + .with_config(|config| { + let base_url = config.model_provider.base_url.clone(); + let mut provider = built_in_model_providers()["openai"].clone(); + provider.name = "Azure".to_string(); + provider.base_url = base_url; + provider.wire_api = WireApi::Responses; + config.model_provider_id = provider.name.clone(); + config.model_provider = provider; + config.web_search_mode = None; + config.features.disable(Feature::WebSearchCached); + config.features.disable(Feature::WebSearchRequest); + }); + let test = builder + .build(&server) + .await + .expect("create test Codex conversation"); + + test.submit_turn_with_policy( + "hello azure default web search", + SandboxPolicy::DangerFullAccess, + ) + .await + .expect("submit turn"); + + let body = resp_mock.single_request().body_json(); + assert_eq!( + has_web_search_tool(&body), + false, + "azure responses requests should disable web_search by default" + ); +} From 13e85b1549ae08c824a16cce57c194837d922cce Mon Sep 17 00:00:00 2001 From: Yuvraj Angad Singh <36276913+yuvrajangadsingh@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:29:20 +0530 Subject: [PATCH 026/139] fix: update file search directory when session CWD changes (#9279) ## Summary Fixes #9041 - Adds update_search_dir() method to FileSearchManager to allow updating the search directory after initialization - Calls this method when the session CWD changes: new session, resume, or fork ## Problem The FileSearchManager was created once with the initial search_dir and never updated. When a user: 1. Starts Codex in a non-git directory (e.g., /tmp/random) 2. Resumes or forks a session from a different workspace 3. The @filename lookup still searched the original directory This caused no matches to be returned even when files existed in the current workspace. ## Solution Update FileSearchManager.search_dir whenever the session working directory changes: - AppEvent::NewSession: Use current config CWD - SessionSelection::Resume: Use resumed session CWD - SessionSelection::Fork: Use forked session CWD ## Test plan - [ ] Start Codex in /tmp/test-dir (non-git) - [ ] Resume a session from a project with actual files - [ ] Verify @filename returns matches from the resumed session directory --------- Co-authored-by: Eric Traut --- codex-rs/tui/src/app.rs | 5 +---- codex-rs/tui/src/file_search.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 7ed9d0b035..7bbbb2f997 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1370,10 +1370,7 @@ impl App { self.shutdown_current_thread().await; self.config = resume_config; tui.set_notification_method(self.config.tui_notification_method); - self.file_search = FileSearchManager::new( - self.config.cwd.clone(), - self.app_event_tx.clone(), - ); + self.file_search.update_search_dir(self.config.cwd.clone()); let init = self.chatwidget_init_for_forked_or_resumed_thread( tui, self.config.clone(), diff --git a/codex-rs/tui/src/file_search.rs b/codex-rs/tui/src/file_search.rs index ff55ac1274..90e2f7f149 100644 --- a/codex-rs/tui/src/file_search.rs +++ b/codex-rs/tui/src/file_search.rs @@ -38,6 +38,17 @@ impl FileSearchManager { } } + /// Updates the directory used for file searches. + /// This should be called when the session's CWD changes on resume. + /// Drops the current session so it will be recreated with the new directory on next query. + pub fn update_search_dir(&mut self, new_dir: PathBuf) { + self.search_dir = new_dir; + #[expect(clippy::unwrap_used)] + let mut st = self.state.lock().unwrap(); + st.session.take(); + st.latest_query.clear(); + } + /// Call whenever the user edits the `@` token. pub fn on_user_query(&self, query: String) { #[expect(clippy::unwrap_used)] From 8d142fd63db4677630b56592d920f7993b354204 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Fri, 30 Jan 2026 15:46:33 -0800 Subject: [PATCH 027/139] Validate CODEX_HOME before resolving (#10249) Summary - require `CODEX_HOME` to point to an existing directory before canonicalizing and surface clear errors otherwise - share the same helper logic in both `core` and `rmcp-client` and add unit tests that cover missing, non-directory, valid, and default paths This addresses #9222 --- codex-rs/Cargo.lock | 13 +- codex-rs/Cargo.toml | 2 + codex-rs/core/Cargo.toml | 2 +- codex-rs/core/src/config/mod.rs | 22 +--- codex-rs/rmcp-client/Cargo.toml | 2 +- codex-rs/rmcp-client/src/find_codex_home.rs | 33 ----- codex-rs/rmcp-client/src/lib.rs | 1 - codex-rs/rmcp-client/src/oauth.rs | 2 +- codex-rs/utils/home-dir/BUILD.bazel | 6 + codex-rs/utils/home-dir/Cargo.toml | 15 +++ codex-rs/utils/home-dir/src/lib.rs | 128 ++++++++++++++++++++ 11 files changed, 168 insertions(+), 58 deletions(-) delete mode 100644 codex-rs/rmcp-client/src/find_codex_home.rs create mode 100644 codex-rs/utils/home-dir/BUILD.bazel create mode 100644 codex-rs/utils/home-dir/Cargo.toml create mode 100644 codex-rs/utils/home-dir/src/lib.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 5cb338be1d..c46c24fb31 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1400,6 +1400,7 @@ dependencies = [ "codex-state", "codex-utils-absolute-path", "codex-utils-cargo-bin", + "codex-utils-home-dir", "codex-utils-pty", "codex-utils-readiness", "codex-utils-string", @@ -1407,7 +1408,6 @@ dependencies = [ "core-foundation 0.9.4", "core_test_support", "ctor 0.6.3", - "dirs", "dunce", "encoding_rs", "env-flags", @@ -1842,7 +1842,7 @@ dependencies = [ "codex-keyring-store", "codex-protocol", "codex-utils-cargo-bin", - "dirs", + "codex-utils-home-dir", "futures", "keyring", "mcp-types", @@ -2007,6 +2007,15 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "codex-utils-home-dir" +version = "0.0.0" +dependencies = [ + "dirs", + "pretty_assertions", + "tempfile", +] + [[package]] name = "codex-utils-image" version = "0.0.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 327e6beeb0..84210838fc 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -43,6 +43,7 @@ members = [ "utils/cache", "utils/image", "utils/json-to-toml", + "utils/home-dir", "utils/pty", "utils/readiness", "utils/string", @@ -102,6 +103,7 @@ codex-utils-cache = { path = "utils/cache" } codex-utils-cargo-bin = { path = "utils/cargo-bin" } codex-utils-image = { path = "utils/image" } codex-utils-json-to-toml = { path = "utils/json-to-toml" } +codex-utils-home-dir = { path = "utils/home-dir" } codex-utils-pty = { path = "utils/pty" } codex-utils-readiness = { path = "utils/readiness" } codex-utils-string = { path = "utils/string" } diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index b336b296c6..1715a04142 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -39,11 +39,11 @@ codex-protocol = { workspace = true } codex-rmcp-client = { workspace = true } codex-state = { workspace = true } codex-utils-absolute-path = { workspace = true } +codex-utils-home-dir = { workspace = true } codex-utils-pty = { workspace = true } codex-utils-readiness = { workspace = true } codex-utils-string = { workspace = true } codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } -dirs = { workspace = true } dunce = { workspace = true } encoding_rs = { workspace = true } env-flags = { workspace = true } diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index bb84cd6755..580f5de9f2 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -57,7 +57,6 @@ use codex_protocol::openai_models::ReasoningEffort; use codex_rmcp_client::OAuthCredentialsStoreMode; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBufGuard; -use dirs::home_dir; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -1753,27 +1752,12 @@ fn toml_uses_deprecated_instructions_file(value: &TomlValue) -> bool { /// specified by the `CODEX_HOME` environment variable. If not set, defaults to /// `~/.codex`. /// -/// - If `CODEX_HOME` is set, the value will be canonicalized and this -/// function will Err if the path does not exist. +/// - If `CODEX_HOME` is set, the value must exist and be a directory. The +/// value will be canonicalized and this function will Err otherwise. /// - If `CODEX_HOME` is not set, this function does not verify that the /// directory exists. pub fn find_codex_home() -> std::io::Result { - // Honor the `CODEX_HOME` environment variable when it is set to allow users - // (and tests) to override the default location. - if let Ok(val) = std::env::var("CODEX_HOME") - && !val.is_empty() - { - return PathBuf::from(val).canonicalize(); - } - - let mut p = home_dir().ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::NotFound, - "Could not find home directory", - ) - })?; - p.push(".codex"); - Ok(p) + codex_utils_home_dir::find_codex_home() } /// Returns the path to the folder where Codex logs are stored. Does not verify diff --git a/codex-rs/rmcp-client/Cargo.toml b/codex-rs/rmcp-client/Cargo.toml index 8aa7512faf..4b2d6964f4 100644 --- a/codex-rs/rmcp-client/Cargo.toml +++ b/codex-rs/rmcp-client/Cargo.toml @@ -15,7 +15,7 @@ axum = { workspace = true, default-features = false, features = [ ] } codex-keyring-store = { workspace = true } codex-protocol = { workspace = true } -dirs = { workspace = true } +codex-utils-home-dir = { workspace = true } futures = { workspace = true, default-features = false, features = ["std"] } keyring = { workspace = true, features = ["crypto-rust"] } mcp-types = { path = "../mcp-types" } diff --git a/codex-rs/rmcp-client/src/find_codex_home.rs b/codex-rs/rmcp-client/src/find_codex_home.rs deleted file mode 100644 index d683ba9d16..0000000000 --- a/codex-rs/rmcp-client/src/find_codex_home.rs +++ /dev/null @@ -1,33 +0,0 @@ -use dirs::home_dir; -use std::path::PathBuf; - -/// This was copied from codex-core but codex-core depends on this crate. -/// TODO: move this to a shared crate lower in the dependency tree. -/// -/// -/// Returns the path to the Codex configuration directory, which can be -/// specified by the `CODEX_HOME` environment variable. If not set, defaults to -/// `~/.codex`. -/// -/// - If `CODEX_HOME` is set, the value will be canonicalized and this -/// function will Err if the path does not exist. -/// - If `CODEX_HOME` is not set, this function does not verify that the -/// directory exists. -pub(crate) fn find_codex_home() -> std::io::Result { - // Honor the `CODEX_HOME` environment variable when it is set to allow users - // (and tests) to override the default location. - if let Ok(val) = std::env::var("CODEX_HOME") - && !val.is_empty() - { - return PathBuf::from(val).canonicalize(); - } - - let mut p = home_dir().ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::NotFound, - "Could not find home directory", - ) - })?; - p.push(".codex"); - Ok(p) -} diff --git a/codex-rs/rmcp-client/src/lib.rs b/codex-rs/rmcp-client/src/lib.rs index e4d1f3b9f0..a10d3b29ae 100644 --- a/codex-rs/rmcp-client/src/lib.rs +++ b/codex-rs/rmcp-client/src/lib.rs @@ -1,5 +1,4 @@ mod auth_status; -mod find_codex_home; mod logging_client_handler; mod oauth; mod perform_oauth_login; diff --git a/codex-rs/rmcp-client/src/oauth.rs b/codex-rs/rmcp-client/src/oauth.rs index a3a256374b..cdb64ff151 100644 --- a/codex-rs/rmcp-client/src/oauth.rs +++ b/codex-rs/rmcp-client/src/oauth.rs @@ -48,7 +48,7 @@ use codex_keyring_store::KeyringStore; use rmcp::transport::auth::AuthorizationManager; use tokio::sync::Mutex; -use crate::find_codex_home::find_codex_home; +use codex_utils_home_dir::find_codex_home; const KEYRING_SERVICE: &str = "Codex MCP Credentials"; const REFRESH_SKEW_MILLIS: u64 = 30_000; diff --git a/codex-rs/utils/home-dir/BUILD.bazel b/codex-rs/utils/home-dir/BUILD.bazel new file mode 100644 index 0000000000..8cae2e9239 --- /dev/null +++ b/codex-rs/utils/home-dir/BUILD.bazel @@ -0,0 +1,6 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "home-dir", + crate_name = "codex_utils_home_dir", +) diff --git a/codex-rs/utils/home-dir/Cargo.toml b/codex-rs/utils/home-dir/Cargo.toml new file mode 100644 index 0000000000..ff263fbed4 --- /dev/null +++ b/codex-rs/utils/home-dir/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "codex-utils-home-dir" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] +dirs = { workspace = true } + +[dev-dependencies] +pretty_assertions = { workspace = true } +tempfile = { workspace = true } diff --git a/codex-rs/utils/home-dir/src/lib.rs b/codex-rs/utils/home-dir/src/lib.rs new file mode 100644 index 0000000000..17be887a83 --- /dev/null +++ b/codex-rs/utils/home-dir/src/lib.rs @@ -0,0 +1,128 @@ +use dirs::home_dir; +use std::path::PathBuf; + +/// Returns the path to the Codex configuration directory, which can be +/// specified by the `CODEX_HOME` environment variable. If not set, defaults to +/// `~/.codex`. +/// +/// - If `CODEX_HOME` is set, the value must exist and be a directory. The +/// value will be canonicalized and this function will Err otherwise. +/// - If `CODEX_HOME` is not set, this function does not verify that the +/// directory exists. +pub fn find_codex_home() -> std::io::Result { + let codex_home_env = std::env::var("CODEX_HOME") + .ok() + .filter(|val| !val.is_empty()); + find_codex_home_from_env(codex_home_env.as_deref()) +} + +fn find_codex_home_from_env(codex_home_env: Option<&str>) -> std::io::Result { + // Honor the `CODEX_HOME` environment variable when it is set to allow users + // (and tests) to override the default location. + match codex_home_env { + Some(val) => { + let path = PathBuf::from(val); + let metadata = std::fs::metadata(&path).map_err(|err| match err.kind() { + std::io::ErrorKind::NotFound => std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("CODEX_HOME points to {val:?}, but that path does not exist"), + ), + _ => std::io::Error::new( + err.kind(), + format!("failed to read CODEX_HOME {val:?}: {err}"), + ), + })?; + + if !metadata.is_dir() { + Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("CODEX_HOME points to {val:?}, but that path is not a directory"), + )) + } else { + path.canonicalize().map_err(|err| { + std::io::Error::new( + err.kind(), + format!("failed to canonicalize CODEX_HOME {val:?}: {err}"), + ) + }) + } + } + None => { + let mut p = home_dir().ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::NotFound, + "Could not find home directory", + ) + })?; + p.push(".codex"); + Ok(p) + } + } +} + +#[cfg(test)] +mod tests { + use super::find_codex_home_from_env; + use dirs::home_dir; + use pretty_assertions::assert_eq; + use std::fs; + use std::io::ErrorKind; + use tempfile::TempDir; + + #[test] + fn find_codex_home_env_missing_path_is_fatal() { + let temp_home = TempDir::new().expect("temp home"); + let missing = temp_home.path().join("missing-codex-home"); + let missing_str = missing + .to_str() + .expect("missing codex home path should be valid utf-8"); + + let err = find_codex_home_from_env(Some(missing_str)).expect_err("missing CODEX_HOME"); + assert_eq!(err.kind(), ErrorKind::NotFound); + assert!( + err.to_string().contains("CODEX_HOME"), + "unexpected error: {err}" + ); + } + + #[test] + fn find_codex_home_env_file_path_is_fatal() { + let temp_home = TempDir::new().expect("temp home"); + let file_path = temp_home.path().join("codex-home.txt"); + fs::write(&file_path, "not a directory").expect("write temp file"); + let file_str = file_path + .to_str() + .expect("file codex home path should be valid utf-8"); + + let err = find_codex_home_from_env(Some(file_str)).expect_err("file CODEX_HOME"); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + assert!( + err.to_string().contains("not a directory"), + "unexpected error: {err}" + ); + } + + #[test] + fn find_codex_home_env_valid_directory_canonicalizes() { + let temp_home = TempDir::new().expect("temp home"); + let temp_str = temp_home + .path() + .to_str() + .expect("temp codex home path should be valid utf-8"); + + let resolved = find_codex_home_from_env(Some(temp_str)).expect("valid CODEX_HOME"); + let expected = temp_home + .path() + .canonicalize() + .expect("canonicalize temp home"); + assert_eq!(resolved, expected); + } + + #[test] + fn find_codex_home_without_env_uses_default_home_dir() { + let resolved = find_codex_home_from_env(None).expect("default CODEX_HOME"); + let mut expected = home_dir().expect("home dir"); + expected.push(".codex"); + assert_eq!(resolved, expected); + } +} From 10ea117ee1e58e21425bf84f4a71620a068dda9d Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 30 Jan 2026 15:50:20 -0800 Subject: [PATCH 028/139] chore: implement Mul for TruncationPolicy (#10272) Codex thought this was a good idea while working on https://github.com/openai/codex/pull/10192. --- codex-rs/core/src/context_manager/history.rs | 2 +- codex-rs/core/src/truncate.rs | 27 +++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 080b701d42..1638a243cc 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -266,7 +266,7 @@ impl ContextManager { } fn process_item(&self, item: &ResponseItem, policy: TruncationPolicy) -> ResponseItem { - let policy_with_serialization_budget = policy.mul(1.2); + let policy_with_serialization_budget = policy * 1.2; match item { ResponseItem::FunctionCallOutput { call_id, output } => { let truncated = diff --git a/codex-rs/core/src/truncate.rs b/codex-rs/core/src/truncate.rs index 8150b994d0..441a157375 100644 --- a/codex-rs/core/src/truncate.rs +++ b/codex-rs/core/src/truncate.rs @@ -34,18 +34,6 @@ impl From for TruncationPolicy { } impl TruncationPolicy { - /// Scale the underlying budget by `multiplier`, rounding up to avoid under-budgeting. - pub fn mul(self, multiplier: f64) -> Self { - match self { - TruncationPolicy::Bytes(bytes) => { - TruncationPolicy::Bytes((bytes as f64 * multiplier).ceil() as usize) - } - TruncationPolicy::Tokens(tokens) => { - TruncationPolicy::Tokens((tokens as f64 * multiplier).ceil() as usize) - } - } - } - /// Returns a token budget derived from this policy. /// /// - For `Tokens`, this is the explicit token limit. @@ -73,6 +61,21 @@ impl TruncationPolicy { } } +impl std::ops::Mul for TruncationPolicy { + type Output = Self; + + fn mul(self, multiplier: f64) -> Self::Output { + match self { + TruncationPolicy::Bytes(bytes) => { + TruncationPolicy::Bytes((bytes as f64 * multiplier).ceil() as usize) + } + TruncationPolicy::Tokens(tokens) => { + TruncationPolicy::Tokens((tokens as f64 * multiplier).ceil() as usize) + } + } + } +} + pub(crate) fn formatted_truncate_text(content: &str, policy: TruncationPolicy) -> String { if content.len() <= policy.byte_budget() { return content.to_string(); From a046481ad908120680aa5b27dca09f540530b95b Mon Sep 17 00:00:00 2001 From: gt-oai Date: Fri, 30 Jan 2026 23:53:41 +0000 Subject: [PATCH 029/139] Wire up cloud reqs in exec, app-server (#10241) We're fetching cloud requirements in TUI in https://github.com/openai/codex/pull/10167. This adds the same fetching in exec and app-server binaries also. --- codex-rs/Cargo.lock | 2 + codex-rs/app-server/Cargo.toml | 1 + .../app-server/src/codex_message_processor.rs | 54 +++++++++--- codex-rs/app-server/src/config_api.rs | 9 +- codex-rs/app-server/src/lib.rs | 36 ++++++-- codex-rs/app-server/src/message_processor.rs | 49 ++++++++--- codex-rs/core/src/config/mod.rs | 21 +++-- codex-rs/core/src/config/service.rs | 13 ++- .../src/config_loader/cloud_requirements.rs | 6 ++ codex-rs/core/src/config_loader/mod.rs | 6 +- codex-rs/core/src/config_loader/tests.rs | 34 ++++---- codex-rs/core/src/skills/manager.rs | 3 +- codex-rs/exec-server/src/posix.rs | 2 +- codex-rs/exec/Cargo.toml | 1 + codex-rs/exec/src/lib.rs | 85 +++++++++++-------- codex-rs/network-proxy/src/state.rs | 14 ++- 16 files changed, 235 insertions(+), 101 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index c46c24fb31..6047ca4725 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1088,6 +1088,7 @@ dependencies = [ "codex-arg0", "codex-backend-client", "codex-chatgpt", + "codex-cloud-requirements", "codex-common", "codex-core", "codex-execpolicy", @@ -1487,6 +1488,7 @@ dependencies = [ "assert_cmd", "clap", "codex-arg0", + "codex-cloud-requirements", "codex-common", "codex-core", "codex-protocol", diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 820edf59c2..11c57ebbc8 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -19,6 +19,7 @@ workspace = true anyhow = { workspace = true } async-trait = { workspace = true } codex-arg0 = { workspace = true } +codex-cloud-requirements = { workspace = true } codex-common = { workspace = true, features = ["cli"] } codex-core = { workspace = true } codex-backend-client = { workspace = true } diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 78f670c049..aae369a19d 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -152,6 +152,7 @@ use codex_core::config::ConfigService; use codex_core::config::edit::ConfigEdit; use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config::types::McpServerTransportConfig; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::default_client::get_codex_user_agent; use codex_core::error::CodexErr; use codex_core::exec::ExecParams; @@ -263,6 +264,7 @@ pub(crate) struct CodexMessageProcessor { codex_linux_sandbox_exe: Option, config: Arc, cli_overrides: Vec<(String, TomlValue)>, + cloud_requirements: CloudRequirementsLoader, conversation_listeners: HashMap>, listener_thread_ids_by_subscription: HashMap, active_login: Arc>>, @@ -281,6 +283,17 @@ pub(crate) enum ApiVersion { V2, } +pub(crate) struct CodexMessageProcessorArgs { + pub(crate) auth_manager: Arc, + pub(crate) thread_manager: Arc, + pub(crate) outgoing: Arc, + pub(crate) codex_linux_sandbox_exe: Option, + pub(crate) config: Arc, + pub(crate) cli_overrides: Vec<(String, TomlValue)>, + pub(crate) cloud_requirements: CloudRequirementsLoader, + pub(crate) feedback: CodexFeedback, +} + impl CodexMessageProcessor { async fn load_thread( &self, @@ -305,15 +318,17 @@ impl CodexMessageProcessor { Ok((thread_id, thread)) } - pub fn new( - auth_manager: Arc, - thread_manager: Arc, - outgoing: Arc, - codex_linux_sandbox_exe: Option, - config: Arc, - cli_overrides: Vec<(String, TomlValue)>, - feedback: CodexFeedback, - ) -> Self { + pub fn new(args: CodexMessageProcessorArgs) -> Self { + let CodexMessageProcessorArgs { + auth_manager, + thread_manager, + outgoing, + codex_linux_sandbox_exe, + config, + cli_overrides, + cloud_requirements, + feedback, + } = args; Self { auth_manager, thread_manager, @@ -321,6 +336,7 @@ impl CodexMessageProcessor { codex_linux_sandbox_exe, config, cli_overrides, + cloud_requirements, conversation_listeners: HashMap::new(), listener_thread_ids_by_subscription: HashMap::new(), active_login: Arc::new(Mutex::new(None)), @@ -333,7 +349,10 @@ impl CodexMessageProcessor { } async fn load_latest_config(&self) -> Result { - Config::load_with_cli_overrides(self.cli_overrides.clone()) + codex_core::config::ConfigBuilder::default() + .cli_overrides(self.cli_overrides.clone()) + .cloud_requirements(self.cloud_requirements.clone()) + .build() .await .map_err(|err| JSONRPCErrorError { code: INTERNAL_ERROR_CODE, @@ -1519,6 +1538,7 @@ impl CodexMessageProcessor { &self.cli_overrides, Some(request_overrides), typesafe_overrides, + &self.cloud_requirements, ) .await { @@ -1603,6 +1623,7 @@ impl CodexMessageProcessor { &self.cli_overrides, config, typesafe_overrides, + &self.cloud_requirements, ) .await { @@ -2350,6 +2371,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -2542,6 +2564,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -3336,6 +3359,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -3524,6 +3548,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -4794,6 +4819,7 @@ async fn derive_config_from_params( cli_overrides: &[(String, TomlValue)], request_overrides: Option>, typesafe_overrides: ConfigOverrides, + cloud_requirements: &CloudRequirementsLoader, ) -> std::io::Result { let merged_cli_overrides = cli_overrides .iter() @@ -4806,7 +4832,11 @@ async fn derive_config_from_params( ) .collect::>(); - Config::load_with_cli_overrides_and_harness_overrides(merged_cli_overrides, typesafe_overrides) + codex_core::config::ConfigBuilder::default() + .cli_overrides(merged_cli_overrides) + .harness_overrides(typesafe_overrides) + .cloud_requirements(cloud_requirements.clone()) + .build() .await } @@ -4815,6 +4845,7 @@ async fn derive_config_for_cwd( request_overrides: Option>, typesafe_overrides: ConfigOverrides, cwd: Option, + cloud_requirements: &CloudRequirementsLoader, ) -> std::io::Result { let merged_cli_overrides = cli_overrides .iter() @@ -4831,6 +4862,7 @@ async fn derive_config_for_cwd( .cli_overrides(merged_cli_overrides) .harness_overrides(typesafe_overrides) .fallback_cwd(cwd) + .cloud_requirements(cloud_requirements.clone()) .build() .await } diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 9207668486..69f2c10df4 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -12,6 +12,7 @@ use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::SandboxMode; use codex_core::config::ConfigService; use codex_core::config::ConfigServiceError; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigRequirementsToml; use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::SandboxModeRequirement as CoreSandboxModeRequirement; @@ -29,9 +30,15 @@ impl ConfigApi { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, + cloud_requirements: CloudRequirementsLoader, ) -> Self { Self { - service: ConfigService::new(codex_home, cli_overrides, loader_overrides), + service: ConfigService::new( + codex_home, + cli_overrides, + loader_overrides, + cloud_requirements, + ), } } diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 5b3d39704c..52ea9325f1 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -1,8 +1,11 @@ #![deny(clippy::print_stdout, clippy::print_stderr)] +use codex_cloud_requirements::cloud_requirements_loader; use codex_common::CliConfigOverrides; +use codex_core::AuthManager; use codex_core::config::Config; use codex_core::config::ConfigBuilder; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::config_loader::LoaderOverrides; use std::io::ErrorKind; @@ -10,6 +13,7 @@ use std::io::Result as IoResult; use std::path::PathBuf; use crate::message_processor::MessageProcessor; +use crate::message_processor::MessageProcessorArgs; use crate::outgoing_message::OutgoingMessage; use crate::outgoing_message::OutgoingMessageSender; use codex_app_server_protocol::ConfigLayerSource; @@ -204,11 +208,32 @@ pub async fn run_main( format!("error parsing -c overrides: {e}"), ) })?; + let cloud_requirements = match ConfigBuilder::default() + .cli_overrides(cli_kv_overrides.clone()) + .loader_overrides(loader_overrides.clone()) + .build() + .await + { + Ok(config) => { + let auth_manager = AuthManager::shared( + config.codex_home.clone(), + false, + config.cli_auth_credentials_store_mode, + ); + cloud_requirements_loader(auth_manager, config.chatgpt_base_url) + } + Err(err) => { + warn!(error = %err, "Failed to preload config for cloud requirements"); + // TODO(gt): Make cloud requirements preload failures blocking once we can fail-closed. + CloudRequirementsLoader::default() + } + }; let loader_overrides_for_config_api = loader_overrides.clone(); let mut config_warnings = Vec::new(); let config = match ConfigBuilder::default() .cli_overrides(cli_kv_overrides.clone()) .loader_overrides(loader_overrides) + .cloud_requirements(cloud_requirements.clone()) .build() .await { @@ -290,15 +315,16 @@ pub async fn run_main( let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx); let cli_overrides: Vec<(String, TomlValue)> = cli_kv_overrides.clone(); let loader_overrides = loader_overrides_for_config_api; - let mut processor = MessageProcessor::new( - outgoing_message_sender, + let mut processor = MessageProcessor::new(MessageProcessorArgs { + outgoing: outgoing_message_sender, codex_linux_sandbox_exe, - std::sync::Arc::new(config), + config: std::sync::Arc::new(config), cli_overrides, loader_overrides, - feedback.clone(), + cloud_requirements: cloud_requirements.clone(), + feedback: feedback.clone(), config_warnings, - ); + }); let mut thread_created_rx = processor.thread_created_receiver(); async move { let mut listen_for_threads = true; diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index ced93c3bdd..21b9a7a562 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::sync::Arc; use crate::codex_message_processor::CodexMessageProcessor; +use crate::codex_message_processor::CodexMessageProcessorArgs; use crate::config_api::ConfigApi; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::outgoing_message::OutgoingMessageSender; @@ -31,6 +32,7 @@ use codex_core::auth::ExternalAuthRefreshReason; use codex_core::auth::ExternalAuthRefresher; use codex_core::auth::ExternalAuthTokens; use codex_core::config::Config; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::LoaderOverrides; use codex_core::default_client::SetOriginatorError; use codex_core::default_client::USER_AGENT_SUFFIX; @@ -106,18 +108,31 @@ pub(crate) struct MessageProcessor { config_warnings: Vec, } +pub(crate) struct MessageProcessorArgs { + pub(crate) outgoing: OutgoingMessageSender, + pub(crate) codex_linux_sandbox_exe: Option, + pub(crate) config: Arc, + pub(crate) cli_overrides: Vec<(String, TomlValue)>, + pub(crate) loader_overrides: LoaderOverrides, + pub(crate) cloud_requirements: CloudRequirementsLoader, + pub(crate) feedback: CodexFeedback, + pub(crate) config_warnings: Vec, +} + impl MessageProcessor { /// Create a new `MessageProcessor`, retaining a handle to the outgoing /// `Sender` so handlers can enqueue messages to be written to stdout. - pub(crate) fn new( - outgoing: OutgoingMessageSender, - codex_linux_sandbox_exe: Option, - config: Arc, - cli_overrides: Vec<(String, TomlValue)>, - loader_overrides: LoaderOverrides, - feedback: CodexFeedback, - config_warnings: Vec, - ) -> Self { + pub(crate) fn new(args: MessageProcessorArgs) -> Self { + let MessageProcessorArgs { + outgoing, + codex_linux_sandbox_exe, + config, + cli_overrides, + loader_overrides, + cloud_requirements, + feedback, + config_warnings, + } = args; let outgoing = Arc::new(outgoing); let auth_manager = AuthManager::shared( config.codex_home.clone(), @@ -133,16 +148,22 @@ impl MessageProcessor { auth_manager.clone(), SessionSource::VSCode, )); - let codex_message_processor = CodexMessageProcessor::new( + let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs { auth_manager, thread_manager, - outgoing.clone(), + outgoing: outgoing.clone(), codex_linux_sandbox_exe, - Arc::clone(&config), - cli_overrides.clone(), + config: Arc::clone(&config), + cli_overrides: cli_overrides.clone(), + cloud_requirements: cloud_requirements.clone(), feedback, + }); + let config_api = ConfigApi::new( + config.codex_home.clone(), + cli_overrides, + loader_overrides, + cloud_requirements, ); - let config_api = ConfigApi::new(config.codex_home.clone(), cli_overrides, loader_overrides); Self { outgoing, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 580f5de9f2..761e5307b4 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -366,7 +366,7 @@ pub struct ConfigBuilder { cli_overrides: Option>, harness_overrides: Option, loader_overrides: Option, - cloud_requirements: Option, + cloud_requirements: CloudRequirementsLoader, fallback_cwd: Option, } @@ -392,7 +392,7 @@ impl ConfigBuilder { } pub fn cloud_requirements(mut self, cloud_requirements: CloudRequirementsLoader) -> Self { - self.cloud_requirements = Some(cloud_requirements); + self.cloud_requirements = cloud_requirements; self } @@ -523,7 +523,7 @@ pub async fn load_config_as_toml_with_cli_overrides( Some(cwd.clone()), &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -627,7 +627,7 @@ pub async fn load_global_mcp_servers( cwd, &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let merged_toml = config_layer_stack.effective_config(); @@ -2627,9 +2627,14 @@ profile = "project" }; let cwd = AbsolutePathBuf::try_from(codex_home.path())?; - let config_layer_stack = - load_config_layers_state(codex_home.path(), Some(cwd), &Vec::new(), overrides, None) - .await?; + let config_layer_stack = load_config_layers_state( + codex_home.path(), + Some(cwd), + &Vec::new(), + overrides, + CloudRequirementsLoader::default(), + ) + .await?; let cfg = deserialize_config_toml_with_base( config_layer_stack.effective_config(), codex_home.path(), @@ -2756,7 +2761,7 @@ profile = "project" Some(cwd), &[("model".to_string(), TomlValue::String("cli".to_string()))], overrides, - None, + CloudRequirementsLoader::default(), ) .await?; diff --git a/codex-rs/core/src/config/service.rs b/codex-rs/core/src/config/service.rs index 82583ee8a8..72393f805b 100644 --- a/codex-rs/core/src/config/service.rs +++ b/codex-rs/core/src/config/service.rs @@ -2,6 +2,7 @@ use super::CONFIG_TOML_FILE; use super::ConfigToml; use crate::config::edit::ConfigEdit; use crate::config::edit::ConfigEditsBuilder; +use crate::config_loader::CloudRequirementsLoader; use crate::config_loader::ConfigLayerEntry; use crate::config_loader::ConfigLayerStack; use crate::config_loader::ConfigLayerStackOrdering; @@ -109,6 +110,7 @@ pub struct ConfigService { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, + cloud_requirements: CloudRequirementsLoader, } impl ConfigService { @@ -116,11 +118,13 @@ impl ConfigService { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, + cloud_requirements: CloudRequirementsLoader, ) -> Self { Self { codex_home, cli_overrides, loader_overrides, + cloud_requirements, } } @@ -129,6 +133,7 @@ impl ConfigService { codex_home, cli_overrides: Vec::new(), loader_overrides: LoaderOverrides::default(), + cloud_requirements: CloudRequirementsLoader::default(), } } @@ -146,6 +151,7 @@ impl ConfigService { .cli_overrides(self.cli_overrides.clone()) .loader_overrides(self.loader_overrides.clone()) .fallback_cwd(Some(cwd.to_path_buf())) + .cloud_requirements(self.cloud_requirements.clone()) .build() .await .map_err(|err| { @@ -376,7 +382,7 @@ impl ConfigService { cwd, &self.cli_overrides, self.loader_overrides.clone(), - None, + self.cloud_requirements.clone(), ) .await } @@ -814,6 +820,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let response = service @@ -896,6 +903,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let result = service @@ -1000,6 +1008,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let error = service @@ -1048,6 +1057,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let response = service @@ -1095,6 +1105,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let result = service diff --git a/codex-rs/core/src/config_loader/cloud_requirements.rs b/codex-rs/core/src/config_loader/cloud_requirements.rs index 7c5e3a2947..3487cc326a 100644 --- a/codex-rs/core/src/config_loader/cloud_requirements.rs +++ b/codex-rs/core/src/config_loader/cloud_requirements.rs @@ -32,6 +32,12 @@ impl fmt::Debug for CloudRequirementsLoader { } } +impl Default for CloudRequirementsLoader { + fn default() -> Self { + Self::new(async { None }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 838db0d5ff..ce220a51e5 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -101,7 +101,7 @@ pub async fn load_config_layers_state( cwd: Option, cli_overrides: &[(String, TomlValue)], overrides: LoaderOverrides, - cloud_requirements: Option, // TODO(gt): Once exec and app-server are wired up, we can remove the option. + cloud_requirements: CloudRequirementsLoader, ) -> io::Result { let mut config_requirements_toml = ConfigRequirementsWithSources::default(); @@ -114,9 +114,7 @@ pub async fn load_config_layers_state( ) .await?; - if let Some(loader) = cloud_requirements - && let Some(requirements) = loader.get().await - { + if let Some(requirements) = cloud_requirements.get().await { config_requirements_toml .merge_unset_fields(RequirementSource::CloudRequirements, requirements); } diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index fd4898c2cd..d2b0cf25f2 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -69,7 +69,7 @@ async fn returns_config_error_for_invalid_user_config_toml() { Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await .expect_err("expected error"); @@ -99,7 +99,7 @@ async fn returns_config_error_for_invalid_managed_config_toml() { Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect_err("expected error"); @@ -188,7 +188,7 @@ extra = true Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect("load config"); @@ -225,7 +225,7 @@ async fn returns_empty_when_all_layers_missing() { Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect("load layers"); @@ -323,7 +323,7 @@ flag = false Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect("load config"); @@ -363,7 +363,7 @@ allowed_sandbox_modes = ["read-only"] ), ), }, - None, + CloudRequirementsLoader::default(), ) .await?; @@ -424,7 +424,7 @@ allowed_approval_policies = ["never"] ), ), }, - None, + CloudRequirementsLoader::default(), ) .await?; @@ -546,7 +546,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()> Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - Some(cloud_requirements), + cloud_requirements, ) .await?; @@ -599,7 +599,7 @@ async fn project_layers_prefer_closest_cwd() -> std::io::Result<()> { Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -731,7 +731,7 @@ async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> s Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -769,7 +769,7 @@ async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::R Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -819,7 +819,7 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -888,7 +888,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result< Some(cwd.clone()), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let project_layers_untrusted: Vec<_> = layers_untrusted @@ -926,7 +926,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result< Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let project_layers_unknown: Vec<_> = layers_unknown @@ -987,7 +987,7 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io:: Some(cwd.clone()), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let project_layers: Vec<_> = layers @@ -1043,7 +1043,7 @@ async fn cli_overrides_with_relative_paths_do_not_break_trust_check() -> std::io Some(cwd), &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -1085,7 +1085,7 @@ async fn project_root_markers_supports_alternate_markers() -> std::io::Result<() Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; diff --git a/codex-rs/core/src/skills/manager.rs b/codex-rs/core/src/skills/manager.rs index f71ecfffa2..3b100991f7 100644 --- a/codex-rs/core/src/skills/manager.rs +++ b/codex-rs/core/src/skills/manager.rs @@ -10,6 +10,7 @@ use tracing::warn; use crate::config::Config; use crate::config::types::SkillsConfig; +use crate::config_loader::CloudRequirementsLoader; use crate::config_loader::LoaderOverrides; use crate::config_loader::load_config_layers_state; use crate::skills::SkillLoadOutcome; @@ -88,7 +89,7 @@ impl SkillsManager { Some(cwd_abs), &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await { diff --git a/codex-rs/exec-server/src/posix.rs b/codex-rs/exec-server/src/posix.rs index 3ec623330c..7f9ce569c6 100644 --- a/codex-rs/exec-server/src/posix.rs +++ b/codex-rs/exec-server/src/posix.rs @@ -241,7 +241,7 @@ async fn load_exec_policy() -> anyhow::Result { cwd, &cli_overrides, overrides, - None, + codex_core::config_loader::CloudRequirementsLoader::default(), ) .await?; diff --git a/codex-rs/exec/Cargo.toml b/codex-rs/exec/Cargo.toml index d4a64772e1..862d8d1694 100644 --- a/codex-rs/exec/Cargo.toml +++ b/codex-rs/exec/Cargo.toml @@ -19,6 +19,7 @@ workspace = true anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } codex-arg0 = { workspace = true } +codex-cloud-requirements = { workspace = true } codex-common = { workspace = true, features = [ "cli", "elapsed", diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index f7176c13b0..396cde190b 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -13,6 +13,7 @@ pub mod exec_events; pub use cli::Cli; pub use cli::Command; pub use cli::ReviewArgs; +use codex_cloud_requirements::cloud_requirements_loader; use codex_common::oss::ensure_oss_provider_ready; use codex_common::oss::get_default_model_for_oss_provider; use codex_common::oss::ollama_chat_deprecation_notice; @@ -24,6 +25,7 @@ use codex_core::OLLAMA_OSS_PROVIDER_ID; use codex_core::ThreadManager; use codex_core::auth::enforce_login_restrictions; use codex_core::config::Config; +use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; use codex_core::config::load_config_as_toml_with_cli_overrides; @@ -159,41 +161,52 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any // we load config.toml here to determine project state. #[allow(clippy::print_stderr)] - let config_toml = { - let codex_home = match find_codex_home() { - Ok(codex_home) => codex_home, - Err(err) => { - eprintln!("Error finding codex home: {err}"); - std::process::exit(1); - } - }; - - match load_config_as_toml_with_cli_overrides( - &codex_home, - &config_cwd, - cli_kv_overrides.clone(), - ) - .await - { - Ok(config_toml) => config_toml, - Err(err) => { - let config_error = err - .get_ref() - .and_then(|err| err.downcast_ref::()) - .map(ConfigLoadError::config_error); - if let Some(config_error) = config_error { - eprintln!( - "Error loading config.toml:\n{}", - format_config_error_with_source(config_error) - ); - } else { - eprintln!("Error loading config.toml: {err}"); - } - std::process::exit(1); - } + let codex_home = match find_codex_home() { + Ok(codex_home) => codex_home, + Err(err) => { + eprintln!("Error finding codex home: {err}"); + std::process::exit(1); } }; + #[allow(clippy::print_stderr)] + let config_toml = match load_config_as_toml_with_cli_overrides( + &codex_home, + &config_cwd, + cli_kv_overrides.clone(), + ) + .await + { + Ok(config_toml) => config_toml, + Err(err) => { + let config_error = err + .get_ref() + .and_then(|err| err.downcast_ref::()) + .map(ConfigLoadError::config_error); + if let Some(config_error) = config_error { + eprintln!( + "Error loading config.toml:\n{}", + format_config_error_with_source(config_error) + ); + } else { + eprintln!("Error loading config.toml: {err}"); + } + std::process::exit(1); + } + }; + + let cloud_auth_manager = AuthManager::shared( + codex_home.clone(), + false, + config_toml.cli_auth_credentials_store.unwrap_or_default(), + ); + let chatgpt_base_url = config_toml + .chatgpt_base_url + .clone() + .unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()); + // TODO(gt): Make cloud requirements failures blocking once we can fail-closed. + let cloud_requirements = cloud_requirements_loader(cloud_auth_manager, chatgpt_base_url); + let model_provider = if oss { let resolved = resolve_oss_provider( oss_provider.as_deref(), @@ -246,8 +259,12 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any additional_writable_roots: add_dir, }; - let config = - Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?; + let config = ConfigBuilder::default() + .cli_overrides(cli_kv_overrides) + .harness_overrides(overrides) + .cloud_requirements(cloud_requirements) + .build() + .await?; if let Err(err) = enforce_login_restrictions(&config) { eprintln!("{err}"); diff --git a/codex-rs/network-proxy/src/state.rs b/codex-rs/network-proxy/src/state.rs index b0c8fdfbcb..20c328c750 100644 --- a/codex-rs/network-proxy/src/state.rs +++ b/codex-rs/network-proxy/src/state.rs @@ -11,6 +11,7 @@ use codex_core::config::CONFIG_TOML_FILE; use codex_core::config::Constrained; use codex_core::config::ConstraintError; use codex_core::config::find_codex_home; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLayerStack; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::config_loader::LoaderOverrides; @@ -31,10 +32,15 @@ pub(crate) async fn build_config_state() -> Result { let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?; let cli_overrides = Vec::new(); let overrides = LoaderOverrides::default(); - let config_layer_stack = - load_config_layers_state(&codex_home, None, &cli_overrides, overrides, None) - .await - .context("failed to load Codex config")?; + let config_layer_stack = load_config_layers_state( + &codex_home, + None, + &cli_overrides, + overrides, + CloudRequirementsLoader::default(), + ) + .await + .context("failed to load Codex config")?; let cfg_path = codex_home.join(CONFIG_TOML_FILE); From 149f3aa27a56c3cbf467a23355704efd9b043b62 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Sat, 31 Jan 2026 00:26:25 +0000 Subject: [PATCH 030/139] Add enforce_residency to requirements (#10263) Add `enforce_residency` to requirements.toml and thread it through to a header on `default_client`. --- .../app-server-protocol/src/protocol/v2.rs | 8 ++++ codex-rs/app-server/README.md | 2 +- codex-rs/app-server/src/config_api.rs | 17 +++++++ codex-rs/app-server/src/message_processor.rs | 4 ++ codex-rs/cloud-requirements/src/lib.rs | 1 + codex-rs/core/src/config/mod.rs | 12 +++++ .../src/config_loader/config_requirements.rs | 46 +++++++++++++++++++ codex-rs/core/src/config_loader/mod.rs | 1 + codex-rs/core/src/config_loader/tests.rs | 8 +++- codex-rs/core/src/default_client.rs | 33 ++++++++++++- codex-rs/exec/src/lib.rs | 2 + codex-rs/tui/src/lib.rs | 2 + 12 files changed, 132 insertions(+), 4 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 8164eb5289..d38a4dd435 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -497,6 +497,14 @@ pub struct ConfigReadResponse { pub struct ConfigRequirements { pub allowed_approval_policies: Option>, pub allowed_sandbox_modes: Option>, + pub enforce_residency: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "lowercase")] +#[ts(export_to = "v2/")] +pub enum ResidencyRequirement { + Us, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 76d1131a2e..8a63ba323f 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -103,7 +103,7 @@ Example (from OpenAI's official VSCode extension): - `config/read` — fetch the effective config on disk after resolving config layering. - `config/value/write` — write a single config key/value to the user's config.toml on disk. - `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk. -- `configRequirements/read` — fetch the loaded requirements allow-lists from `requirements.toml` and/or MDM (or `null` if none are configured). +- `configRequirements/read` — fetch the loaded requirements allow-lists and `enforceResidency` from `requirements.toml` and/or MDM (or `null` if none are configured). ### Example: Start or resume a thread diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 69f2c10df4..5a43d6e528 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -15,6 +15,7 @@ use codex_core::config::ConfigServiceError; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigRequirementsToml; use codex_core::config_loader::LoaderOverrides; +use codex_core::config_loader::ResidencyRequirement as CoreResidencyRequirement; use codex_core::config_loader::SandboxModeRequirement as CoreSandboxModeRequirement; use serde_json::json; use std::path::PathBuf; @@ -91,6 +92,9 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR .filter_map(map_sandbox_mode_requirement_to_api) .collect() }), + enforce_residency: requirements + .enforce_residency + .map(map_residency_requirement_to_api), } } @@ -103,6 +107,14 @@ fn map_sandbox_mode_requirement_to_api(mode: CoreSandboxModeRequirement) -> Opti } } +fn map_residency_requirement_to_api( + residency: CoreResidencyRequirement, +) -> codex_app_server_protocol::ResidencyRequirement { + match residency { + CoreResidencyRequirement::Us => codex_app_server_protocol::ResidencyRequirement::Us, + } +} + fn map_error(err: ConfigServiceError) -> JSONRPCErrorError { if let Some(code) = err.write_error_code() { return config_write_error(code, err.to_string()); @@ -144,6 +156,7 @@ mod tests { ]), mcp_servers: None, rules: None, + enforce_residency: Some(CoreResidencyRequirement::Us), }; let mapped = map_requirements_toml_to_api(requirements); @@ -159,5 +172,9 @@ mod tests { mapped.allowed_sandbox_modes, Some(vec![SandboxMode::ReadOnly]), ); + assert_eq!( + mapped.enforce_residency, + Some(codex_app_server_protocol::ResidencyRequirement::Us), + ); } } diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 21b9a7a562..b1c899bf1d 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -37,6 +37,7 @@ use codex_core::config_loader::LoaderOverrides; use codex_core::default_client::SetOriginatorError; use codex_core::default_client::USER_AGENT_SUFFIX; use codex_core::default_client::get_codex_user_agent; +use codex_core::default_client::set_default_client_residency_requirement; use codex_core::default_client::set_default_originator; use codex_feedback::CodexFeedback; use codex_protocol::ThreadId; @@ -104,6 +105,7 @@ pub(crate) struct MessageProcessor { outgoing: Arc, codex_message_processor: CodexMessageProcessor, config_api: ConfigApi, + config: Arc, initialized: bool, config_warnings: Vec, } @@ -169,6 +171,7 @@ impl MessageProcessor { outgoing, codex_message_processor, config_api, + config, initialized: false, config_warnings, } @@ -241,6 +244,7 @@ impl MessageProcessor { } } } + set_default_client_residency_requirement(self.config.enforce_residency.value()); let user_agent_suffix = format!("{name}; {version}"); if let Ok(mut suffix) = USER_AGENT_SUFFIX.lock() { *suffix = Some(user_agent_suffix); diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index ed1bb051d5..00a2814cbb 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -315,6 +315,7 @@ mod tests { allowed_sandbox_modes: None, mcp_servers: None, rules: None, + enforce_residency: None, }) ); } diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 761e5307b4..6af91109dc 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -24,6 +24,7 @@ use crate::config_loader::ConfigRequirements; use crate::config_loader::LoaderOverrides; use crate::config_loader::McpServerIdentity; use crate::config_loader::McpServerRequirement; +use crate::config_loader::ResidencyRequirement; use crate::config_loader::Sourced; use crate::config_loader::load_config_layers_state; use crate::features::Feature; @@ -140,6 +141,11 @@ pub struct Config { pub sandbox_policy: Constrained, + /// enforce_residency means web traffic cannot be routed outside of a + /// particular geography. HTTP clients should direct their requests + /// using backend-specific headers or URLs to enforce this. + pub enforce_residency: Constrained>, + /// True if the user passed in an override or set a value in config.toml /// for either of approval_policy or sandbox_mode. pub did_user_set_custom_approval_policy_or_sandbox_mode: bool, @@ -1516,6 +1522,7 @@ impl Config { sandbox_policy: mut constrained_sandbox_policy, mcp_servers, exec_policy: _, + enforce_residency, } = requirements; constrained_approval_policy @@ -1538,6 +1545,7 @@ impl Config { cwd: resolved_cwd, approval_policy: constrained_approval_policy, sandbox_policy: constrained_sandbox_policy, + enforce_residency, did_user_set_custom_approval_policy_or_sandbox_mode, forced_auto_mode_downgraded_on_windows, shell_environment_policy, @@ -3771,6 +3779,7 @@ model_verbosity = "high" model_provider: fixture.openai_provider.clone(), approval_policy: Constrained::allow_any(AskForApproval::Never), sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), + enforce_residency: Constrained::allow_any(None), did_user_set_custom_approval_policy_or_sandbox_mode: true, forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), @@ -3855,6 +3864,7 @@ model_verbosity = "high" model_provider: fixture.openai_chat_completions_provider.clone(), approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted), sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), + enforce_residency: Constrained::allow_any(None), did_user_set_custom_approval_policy_or_sandbox_mode: true, forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), @@ -3954,6 +3964,7 @@ model_verbosity = "high" model_provider: fixture.openai_provider.clone(), approval_policy: Constrained::allow_any(AskForApproval::OnFailure), sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), + enforce_residency: Constrained::allow_any(None), did_user_set_custom_approval_policy_or_sandbox_mode: true, forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), @@ -4039,6 +4050,7 @@ model_verbosity = "high" model_provider: fixture.openai_provider.clone(), approval_policy: Constrained::allow_any(AskForApproval::OnFailure), sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), + enforce_residency: Constrained::allow_any(None), did_user_set_custom_approval_policy_or_sandbox_mode: true, forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), diff --git a/codex-rs/core/src/config_loader/config_requirements.rs b/codex-rs/core/src/config_loader/config_requirements.rs index 6b9184a539..478c74193e 100644 --- a/codex-rs/core/src/config_loader/config_requirements.rs +++ b/codex-rs/core/src/config_loader/config_requirements.rs @@ -52,6 +52,7 @@ pub struct ConfigRequirements { pub sandbox_policy: Constrained, pub mcp_servers: Option>>, pub(crate) exec_policy: Option>, + pub enforce_residency: Constrained>, } impl Default for ConfigRequirements { @@ -61,6 +62,7 @@ impl Default for ConfigRequirements { sandbox_policy: Constrained::allow_any(SandboxPolicy::ReadOnly), mcp_servers: None, exec_policy: None, + enforce_residency: Constrained::allow_any(None), } } } @@ -84,6 +86,7 @@ pub struct ConfigRequirementsToml { pub allowed_sandbox_modes: Option>, pub mcp_servers: Option>, pub rules: Option, + pub enforce_residency: Option, } /// Value paired with the requirement source it came from, for better error @@ -114,6 +117,7 @@ pub struct ConfigRequirementsWithSources { pub allowed_sandbox_modes: Option>>, pub mcp_servers: Option>>, pub rules: Option>, + pub enforce_residency: Option>, } impl ConfigRequirementsWithSources { @@ -146,6 +150,7 @@ impl ConfigRequirementsWithSources { allowed_sandbox_modes, mcp_servers, rules, + enforce_residency, } ); } @@ -156,12 +161,14 @@ impl ConfigRequirementsWithSources { allowed_sandbox_modes, mcp_servers, rules, + enforce_residency, } = self; ConfigRequirementsToml { allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value), allowed_sandbox_modes: allowed_sandbox_modes.map(|sourced| sourced.value), mcp_servers: mcp_servers.map(|sourced| sourced.value), rules: rules.map(|sourced| sourced.value), + enforce_residency: enforce_residency.map(|sourced| sourced.value), } } } @@ -193,12 +200,19 @@ impl From for SandboxModeRequirement { } } +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ResidencyRequirement { + Us, +} + impl ConfigRequirementsToml { pub fn is_empty(&self) -> bool { self.allowed_approval_policies.is_none() && self.allowed_sandbox_modes.is_none() && self.mcp_servers.is_none() && self.rules.is_none() + && self.enforce_residency.is_none() } } @@ -211,6 +225,7 @@ impl TryFrom for ConfigRequirements { allowed_sandbox_modes, mcp_servers, rules, + enforce_residency, } = toml; let approval_policy: Constrained = match allowed_approval_policies { @@ -298,11 +313,33 @@ impl TryFrom for ConfigRequirements { None => None, }; + let enforce_residency: Constrained> = match enforce_residency { + Some(Sourced { + value: residency, + source: requirement_source, + }) => { + let required = Some(residency); + Constrained::new(required, move |candidate| { + if candidate == &required { + Ok(()) + } else { + Err(ConstraintError::InvalidValue { + field_name: "enforce_residency", + candidate: format!("{candidate:?}"), + allowed: format!("{required:?}"), + requirement_source: requirement_source.clone(), + }) + } + })? + } + None => Constrained::allow_any(None), + }; Ok(ConfigRequirements { approval_policy, sandbox_policy, mcp_servers, exec_policy, + enforce_residency, }) } } @@ -329,6 +366,7 @@ mod tests { allowed_sandbox_modes, mcp_servers, rules, + enforce_residency, } = toml; ConfigRequirementsWithSources { allowed_approval_policies: allowed_approval_policies @@ -337,6 +375,8 @@ mod tests { .map(|value| Sourced::new(value, RequirementSource::Unknown)), mcp_servers: mcp_servers.map(|value| Sourced::new(value, RequirementSource::Unknown)), rules: rules.map(|value| Sourced::new(value, RequirementSource::Unknown)), + enforce_residency: enforce_residency + .map(|value| Sourced::new(value, RequirementSource::Unknown)), } } @@ -350,6 +390,8 @@ mod tests { SandboxModeRequirement::WorkspaceWrite, SandboxModeRequirement::DangerFullAccess, ]; + let enforce_residency = ResidencyRequirement::Us; + let enforce_source = source.clone(); // Intentionally constructed without `..Default::default()` so adding a new field to // `ConfigRequirementsToml` forces this test to be updated. @@ -358,6 +400,7 @@ mod tests { allowed_sandbox_modes: Some(allowed_sandbox_modes.clone()), mcp_servers: None, rules: None, + enforce_residency: Some(enforce_residency), }; target.merge_unset_fields(source.clone(), other); @@ -372,6 +415,7 @@ mod tests { allowed_sandbox_modes: Some(Sourced::new(allowed_sandbox_modes, source)), mcp_servers: None, rules: None, + enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)), } ); } @@ -401,6 +445,7 @@ mod tests { allowed_sandbox_modes: None, mcp_servers: None, rules: None, + enforce_residency: None, } ); Ok(()) @@ -438,6 +483,7 @@ mod tests { allowed_sandbox_modes: None, mcp_servers: None, rules: None, + enforce_residency: None, } ); Ok(()) diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index ce220a51e5..d48bda18f6 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -37,6 +37,7 @@ pub use config_requirements::ConfigRequirementsToml; pub use config_requirements::McpServerIdentity; pub use config_requirements::McpServerRequirement; pub use config_requirements::RequirementSource; +pub use config_requirements::ResidencyRequirement; pub use config_requirements::SandboxModeRequirement; pub use config_requirements::Sourced; pub use diagnostics::ConfigError; diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index d2b0cf25f2..cbd49f131c 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -451,6 +451,7 @@ async fn load_requirements_toml_produces_expected_constraints() -> anyhow::Resul &requirements_file, r#" allowed_approval_policies = ["never", "on-request"] +enforce_residency = "us" "#, ) .await?; @@ -465,7 +466,6 @@ allowed_approval_policies = ["never", "on-request"] .cloned(), Some(vec![AskForApproval::Never, AskForApproval::OnRequest]) ); - let config_requirements: ConfigRequirements = config_requirements_toml.try_into()?; assert_eq!( config_requirements.approval_policy.value(), @@ -480,6 +480,10 @@ allowed_approval_policies = ["never", "on-request"] .can_set(&AskForApproval::OnFailure) .is_err() ); + assert_eq!( + config_requirements.enforce_residency.value(), + Some(crate::config_loader::ResidencyRequirement::Us) + ); Ok(()) } @@ -503,6 +507,7 @@ allowed_approval_policies = ["on-request"] allowed_sandbox_modes: None, mcp_servers: None, rules: None, + enforce_residency: None, }, ); load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?; @@ -537,6 +542,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()> allowed_sandbox_modes: None, mcp_servers: None, rules: None, + enforce_residency: None, }; let expected = requirements.clone(); let cloud_requirements = CloudRequirementsLoader::new(async move { Some(requirements) }); diff --git a/codex-rs/core/src/default_client.rs b/codex-rs/core/src/default_client.rs index 67a5daf1b1..94ecd8fcec 100644 --- a/codex-rs/core/src/default_client.rs +++ b/codex-rs/core/src/default_client.rs @@ -1,6 +1,8 @@ +use crate::config_loader::ResidencyRequirement; use crate::spawn::CODEX_SANDBOX_ENV_VAR; use codex_client::CodexHttpClient; pub use codex_client::CodexRequestBuilder; +use reqwest::header::HeaderMap; use reqwest::header::HeaderValue; use std::sync::LazyLock; use std::sync::Mutex; @@ -24,6 +26,7 @@ use std::sync::RwLock; pub static USER_AGENT_SUFFIX: LazyLock>> = LazyLock::new(|| Mutex::new(None)); pub const DEFAULT_ORIGINATOR: &str = "codex_cli_rs"; pub const CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR: &str = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE"; +pub const RESIDENCY_HEADER_NAME: &str = "x-openai-internal-codex-residency"; #[derive(Debug, Clone)] pub struct Originator { @@ -31,6 +34,8 @@ pub struct Originator { pub header_value: HeaderValue, } static ORIGINATOR: LazyLock>> = LazyLock::new(|| RwLock::new(None)); +static REQUIREMENTS_RESIDENCY: LazyLock>> = + LazyLock::new(|| RwLock::new(None)); #[derive(Debug)] pub enum SetOriginatorError { @@ -74,6 +79,14 @@ pub fn set_default_originator(value: String) -> Result<(), SetOriginatorError> { Ok(()) } +pub fn set_default_client_residency_requirement(enforce_residency: Option) { + let Ok(mut guard) = REQUIREMENTS_RESIDENCY.write() else { + tracing::warn!("Failed to acquire requirements residency lock"); + return; + }; + *guard = enforce_residency; +} + pub fn originator() -> Originator { if let Ok(guard) = ORIGINATOR.read() && let Some(originator) = guard.as_ref() @@ -166,10 +179,17 @@ pub fn create_client() -> CodexHttpClient { } pub fn build_reqwest_client() -> reqwest::Client { - use reqwest::header::HeaderMap; - let mut headers = HeaderMap::new(); headers.insert("originator", originator().header_value); + if let Ok(guard) = REQUIREMENTS_RESIDENCY.read() + && let Some(requirement) = guard.as_ref() + && !headers.contains_key(RESIDENCY_HEADER_NAME) + { + let value = match requirement { + ResidencyRequirement::Us => HeaderValue::from_static("us"), + }; + headers.insert(RESIDENCY_HEADER_NAME, value); + } let ua = get_codex_user_agent(); let mut builder = reqwest::Client::builder() @@ -214,6 +234,8 @@ mod tests { async fn test_create_client_sets_default_headers() { skip_if_no_network!(); + set_default_client_residency_requirement(Some(ResidencyRequirement::Us)); + use wiremock::Mock; use wiremock::MockServer; use wiremock::ResponseTemplate; @@ -256,6 +278,13 @@ mod tests { .get("user-agent") .expect("user-agent header missing"); assert_eq!(ua_header.to_str().unwrap(), expected_ua); + + let residency_header = headers + .get(RESIDENCY_HEADER_NAME) + .expect("residency header missing"); + assert_eq!(residency_header.to_str().unwrap(), "us"); + + set_default_client_residency_requirement(None); } #[test] diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 396cde190b..e0f39beb23 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -66,6 +66,7 @@ use uuid::Uuid; use crate::cli::Command as ExecCommand; use crate::event_processor::CodexStatus; use crate::event_processor::EventProcessor; +use codex_core::default_client::set_default_client_residency_requirement; use codex_core::default_client::set_default_originator; use codex_core::find_thread_path_by_id_str; use codex_core::find_thread_path_by_name_str; @@ -265,6 +266,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any .cloud_requirements(cloud_requirements) .build() .await?; + set_default_client_residency_requirement(config.enforce_residency.value()); if let Err(err) = enforce_login_restrictions(&config) { eprintln!("{err}"); diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 1f34ed6049..82a06a7032 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -27,6 +27,7 @@ use codex_core::config::resolve_oss_provider; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::format_config_error_with_source; +use codex_core::default_client::set_default_client_residency_requirement; use codex_core::find_thread_path_by_id_str; use codex_core::find_thread_path_by_name_str; use codex_core::path_utils; @@ -276,6 +277,7 @@ pub async fn run_main( cloud_requirements.clone(), ) .await; + set_default_client_residency_requirement(config.enforce_residency.value()); if let Some(warning) = add_dir_warning_message(&cli.add_dir, config.sandbox_policy.get()) { #[allow(clippy::print_stderr)] From eb86663dcb0f19df721b3a11f5501f5988576b58 Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Fri, 30 Jan 2026 16:37:56 -0800 Subject: [PATCH 031/139] add missing fields to WebSearchAction and update app-server types (#10276) - add `WebSearchAction` to app-server v2 types - add `queries` to `WebSearchAction::Search` type Updated tests. --- .../app-server-protocol/src/protocol/v2.rs | 52 +++++++++++++++++-- codex-rs/app-server/README.md | 2 +- codex-rs/core/src/event_mapping.rs | 2 + codex-rs/core/src/web_search.rs | 17 +++++- codex-rs/core/tests/suite/client.rs | 1 + codex-rs/core/tests/suite/items.rs | 1 + .../tests/event_processor_with_json_output.rs | 2 + codex-rs/protocol/src/models.rs | 7 ++- codex-rs/protocol/src/protocol.rs | 1 + codex-rs/tui/src/history_cell.rs | 20 +++++-- 10 files changed, 95 insertions(+), 10 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index d38a4dd435..f99659cd4d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2121,7 +2121,11 @@ pub enum ThreadItem { }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] - WebSearch { id: String, query: String }, + WebSearch { + id: String, + query: String, + action: Option, + }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ImageView { id: String, path: String }, @@ -2136,6 +2140,42 @@ pub enum ThreadItem { ContextCompaction { id: String }, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(tag = "type", rename_all = "camelCase")] +#[ts(tag = "type", rename_all = "camelCase")] +pub enum WebSearchAction { + Search { + query: Option, + queries: Option>, + }, + OpenPage { + url: Option, + }, + FindInPage { + url: Option, + pattern: Option, + }, + #[serde(other)] + Other, +} + +impl From for WebSearchAction { + fn from(value: codex_protocol::models::WebSearchAction) -> Self { + match value { + codex_protocol::models::WebSearchAction::Search { query, queries } => { + WebSearchAction::Search { query, queries } + } + codex_protocol::models::WebSearchAction::OpenPage { url } => { + WebSearchAction::OpenPage { url } + } + codex_protocol::models::WebSearchAction::FindInPage { url, pattern } => { + WebSearchAction::FindInPage { url, pattern } + } + codex_protocol::models::WebSearchAction::Other => WebSearchAction::Other, + } + } +} + impl From for ThreadItem { fn from(value: CoreTurnItem) -> Self { match value { @@ -2165,6 +2205,7 @@ impl From for ThreadItem { CoreTurnItem::WebSearch(search) => ThreadItem::WebSearch { id: search.id, query: search.query, + action: Some(WebSearchAction::from(search.action)), }, CoreTurnItem::ContextCompaction(compaction) => { ThreadItem::ContextCompaction { id: compaction.id } @@ -2818,7 +2859,7 @@ mod tests { use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; use codex_protocol::items::WebSearchItem; - use codex_protocol::models::WebSearchAction; + use codex_protocol::models::WebSearchAction as CoreWebSearchAction; use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess; use codex_protocol::user_input::UserInput as CoreUserInput; use pretty_assertions::assert_eq; @@ -2934,8 +2975,9 @@ mod tests { let search_item = TurnItem::WebSearch(WebSearchItem { id: "search-1".to_string(), query: "docs".to_string(), - action: WebSearchAction::Search { + action: CoreWebSearchAction::Search { query: Some("docs".to_string()), + queries: None, }, }); @@ -2944,6 +2986,10 @@ mod tests { ThreadItem::WebSearch { id: "search-1".to_string(), query: "docs".to_string(), + action: Some(WebSearchAction::Search { + query: Some("docs".to_string()), + queries: None, + }), } ); } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 8a63ba323f..1963c0cbdf 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -450,7 +450,7 @@ Today both notifications carry an empty `items` array even when item events were - `fileChange` — `{id, changes, status}` describing proposed edits; `changes` list `{path, kind, diff}` and `status` is `inProgress`, `completed`, `failed`, or `declined`. - `mcpToolCall` — `{id, server, tool, status, arguments, result?, error?}` describing MCP calls; `status` is `inProgress`, `completed`, or `failed`. - `collabToolCall` — `{id, tool, status, senderThreadId, receiverThreadId?, newThreadId?, prompt?, agentStatus?}` describing collab tool calls (`spawn_agent`, `send_input`, `wait`, `close_agent`); `status` is `inProgress`, `completed`, or `failed`. -- `webSearch` — `{id, query}` for a web search request issued by the agent. +- `webSearch` — `{id, query, action?}` for a web search request issued by the agent; `action` mirrors the Responses API web_search action payload (`search`, `open_page`, `find_in_page`) and may be omitted until completion. - `imageView` — `{id, path}` emitted when the agent invokes the image viewer tool. - `enteredReviewMode` — `{id, review}` sent when the reviewer starts; `review` is a short user-facing label such as `"current changes"` or the requested target description. - `exitedReviewMode` — `{id, review}` emitted when the reviewer finishes; `review` is the full plain-text review (usually, overall notes plus bullet point findings). diff --git a/codex-rs/core/src/event_mapping.rs b/codex-rs/core/src/event_mapping.rs index 32382aad71..35804fd47c 100644 --- a/codex-rs/core/src/event_mapping.rs +++ b/codex-rs/core/src/event_mapping.rs @@ -426,6 +426,7 @@ mod tests { status: Some("completed".to_string()), action: Some(WebSearchAction::Search { query: Some("weather".to_string()), + queries: None, }), }; @@ -439,6 +440,7 @@ mod tests { query: "weather".to_string(), action: WebSearchAction::Search { query: Some("weather".to_string()), + queries: None, }, } ), diff --git a/codex-rs/core/src/web_search.rs b/codex-rs/core/src/web_search.rs index 458758b70e..d3c895c5fa 100644 --- a/codex-rs/core/src/web_search.rs +++ b/codex-rs/core/src/web_search.rs @@ -1,8 +1,23 @@ use codex_protocol::models::WebSearchAction; +fn search_action_detail(query: &Option, queries: &Option>) -> String { + query.clone().filter(|q| !q.is_empty()).unwrap_or_else(|| { + let items = queries.as_ref(); + let first = items + .and_then(|queries| queries.first()) + .cloned() + .unwrap_or_default(); + if items.is_some_and(|queries| queries.len() > 1) && !first.is_empty() { + format!("{first} ...") + } else { + first + } + }) +} + pub fn web_search_action_detail(action: &WebSearchAction) -> String { match action { - WebSearchAction::Search { query } => query.clone().unwrap_or_default(), + WebSearchAction::Search { query, queries } => search_action_detail(query, queries), WebSearchAction::OpenPage { url } => url.clone().unwrap_or_default(), WebSearchAction::FindInPage { url, pattern } => match (pattern, url) { (Some(pattern), Some(url)) => format!("'{pattern}' in {url}"), diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 72608a44d8..150513d000 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1216,6 +1216,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { status: Some("completed".into()), action: Some(WebSearchAction::Search { query: Some("weather".into()), + queries: None, }), }); prompt.input.push(ResponseItem::FunctionCall { diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index 7ef73dce94..842122dfea 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -255,6 +255,7 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> { completed.action, WebSearchAction::Search { query: Some("weather seattle".to_string()), + queries: None, } ); diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index e405195e7e..47b7a9f331 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -131,6 +131,7 @@ fn web_search_end_emits_item_completed() { let query = "rust async await".to_string(); let action = WebSearchAction::Search { query: Some(query.clone()), + queries: None, }; let out = ep.collect_thread_events(&event( "w1", @@ -195,6 +196,7 @@ fn web_search_begin_then_end_reuses_item_id() { }; let action = WebSearchAction::Search { query: Some("rust async await".to_string()), + queries: None, }; let end = ep.collect_thread_events(&event( "w1", diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 759ff5aae4..8e67fc40fc 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -620,6 +620,9 @@ pub enum WebSearchAction { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] query: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + queries: Option>, }, OpenPage { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1132,12 +1135,14 @@ mod tests { "status": "completed", "action": { "type": "search", - "query": "weather seattle" + "query": "weather seattle", + "queries": ["weather seattle", "seattle weather now"] } }"#, None, Some(WebSearchAction::Search { query: Some("weather seattle".into()), + queries: Some(vec!["weather seattle".into(), "seattle weather now".into()]), }), Some("completed".into()), true, diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 929d7d73c7..6a6939620f 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2469,6 +2469,7 @@ mod tests { query: "find docs".into(), action: WebSearchAction::Search { query: Some("find docs".into()), + queries: None, }, }), }; diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 39482bc1f3..74fc650908 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -2294,7 +2294,10 @@ mod tests { let cell = new_web_search_call( "call-1".to_string(), query.clone(), - WebSearchAction::Search { query: Some(query) }, + WebSearchAction::Search { + query: Some(query), + queries: None, + }, ); let rendered = render_lines(&cell.display_lines(64)).join("\n"); @@ -2308,7 +2311,10 @@ mod tests { let cell = new_web_search_call( "call-1".to_string(), query.clone(), - WebSearchAction::Search { query: Some(query) }, + WebSearchAction::Search { + query: Some(query), + queries: None, + }, ); let rendered = render_lines(&cell.display_lines(64)); @@ -2327,7 +2333,10 @@ mod tests { let cell = new_web_search_call( "call-1".to_string(), query.clone(), - WebSearchAction::Search { query: Some(query) }, + WebSearchAction::Search { + query: Some(query), + queries: None, + }, ); let rendered = render_lines(&cell.display_lines(64)); @@ -2341,7 +2350,10 @@ mod tests { let cell = new_web_search_call( "call-1".to_string(), query.clone(), - WebSearchAction::Search { query: Some(query) }, + WebSearchAction::Search { + query: Some(query), + queries: None, + }, ); let rendered = render_lines(&cell.transcript_lines(64)).join("\n"); From 47faa1594c41fd026742c385264c1007fc5913ca Mon Sep 17 00:00:00 2001 From: gt-oai Date: Sat, 31 Jan 2026 02:57:42 +0000 Subject: [PATCH 032/139] Turn on cloud requirements for business too (#10283) Need to check "enterprise" and "business" --- codex-rs/cloud-requirements/src/lib.rs | 36 ++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 00a2814cbb..174cf96720 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -1,7 +1,8 @@ //! Cloud-hosted config requirements for Codex. //! //! This crate fetches `requirements.toml` data from the backend as an alternative to loading it -//! from the local filesystem. It only applies to Enterprise ChatGPT customers. +//! from the local filesystem. It only applies to Business (aka Enterprise CBP) or Enterprise ChatGPT +//! customers. //! //! Today, fetching is best-effort: on error or timeout, Codex continues without cloud requirements. //! We expect to tighten this so that Enterprise ChatGPT customers must successfully fetch these @@ -119,7 +120,12 @@ impl CloudRequirementsService { async fn fetch(&self) -> Option { let auth = self.auth_manager.auth().await?; - if !(auth.is_chatgpt_auth() && auth.account_plan_type() == Some(PlanType::Enterprise)) { + if !auth.is_chatgpt_auth() + || !matches!( + auth.account_plan_type(), + Some(PlanType::Business | PlanType::Enterprise) + ) + { return None; } @@ -269,10 +275,9 @@ mod tests { } #[tokio::test] - async fn fetch_cloud_requirements_skips_non_enterprise_plan() { - let auth_manager = auth_manager_with_plan("pro"); + async fn fetch_cloud_requirements_skips_non_business_or_enterprise_plan() { let service = CloudRequirementsService::new( - auth_manager, + auth_manager_with_plan("pro"), Arc::new(StaticFetcher { contents: None }), CLOUD_REQUIREMENTS_TIMEOUT, ); @@ -280,6 +285,27 @@ mod tests { assert!(result.is_none()); } + #[tokio::test] + async fn fetch_cloud_requirements_allows_business_plan() { + let service = CloudRequirementsService::new( + auth_manager_with_plan("business"), + Arc::new(StaticFetcher { + contents: Some("allowed_approval_policies = [\"never\"]".to_string()), + }), + CLOUD_REQUIREMENTS_TIMEOUT, + ); + assert_eq!( + service.fetch().await, + Some(ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::Never]), + allowed_sandbox_modes: None, + mcp_servers: None, + rules: None, + enforce_residency: None, + }) + ); + } + #[tokio::test] async fn fetch_cloud_requirements_handles_missing_contents() { let result = parse_for_fetch(None); From 9327e99b2816d839ff0049dd3741611ae57cb1c9 Mon Sep 17 00:00:00 2001 From: Ruyut Date: Sat, 31 Jan 2026 14:11:02 +0800 Subject: [PATCH 033/139] Fix minor typos in comments and documentation (#10287) ## Summary I have read the contribution guidelines. All changes in this PR are limited to text corrections and do not modify any business logic, runtime behavior, or user-facing functionality. ## Details This PR fixes several minor typos, including: - `create` -> `crate` - `analagous` -> `analogous` - `apply-patch` -> `apply_patch` - `codecs` -> `codex` - ` '/" ` -> ` '/' ` - `Respesent` -> `Represent` --- .github/codex/labels/codex-rust-review.md | 4 ++-- codex-rs/apply-patch/src/standalone_executable.rs | 2 +- codex-rs/cloud-requirements/src/lib.rs | 2 +- codex-rs/tui/src/bottom_pane/command_popup.rs | 2 +- sdk/typescript/src/thread.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/codex/labels/codex-rust-review.md b/.github/codex/labels/codex-rust-review.md index ae82d272d7..ae74953a5c 100644 --- a/.github/codex/labels/codex-rust-review.md +++ b/.github/codex/labels/codex-rust-review.md @@ -15,10 +15,10 @@ Things to look out for when doing the review: ## Code Organization -- Each create in the Cargo workspace in `codex-rs` has a specific purpose: make a note if you believe new code is not introduced in the correct crate. +- Each crate in the Cargo workspace in `codex-rs` has a specific purpose: make a note if you believe new code is not introduced in the correct crate. - When possible, try to keep the `core` crate as small as possible. Non-core but shared logic is often a good candidate for `codex-rs/common`. - Be wary of large files and offer suggestions for how to break things into more reasonably-sized files. -- Rust files should generally be organized such that the public parts of the API appear near the top of the file and helper functions go below. This is analagous to the "inverted pyramid" structure that is favored in journalism. +- Rust files should generally be organized such that the public parts of the API appear near the top of the file and helper functions go below. This is analogous to the "inverted pyramid" structure that is favored in journalism. ## Assertions in Tests diff --git a/codex-rs/apply-patch/src/standalone_executable.rs b/codex-rs/apply-patch/src/standalone_executable.rs index ba31465c8d..d77a82fa95 100644 --- a/codex-rs/apply-patch/src/standalone_executable.rs +++ b/codex-rs/apply-patch/src/standalone_executable.rs @@ -27,7 +27,7 @@ pub fn run_main() -> i32 { match std::io::stdin().read_to_string(&mut buf) { Ok(_) => { if buf.is_empty() { - eprintln!("Usage: apply_patch 'PATCH'\n echo 'PATCH' | apply-patch"); + eprintln!("Usage: apply_patch 'PATCH'\n echo 'PATCH' | apply_patch"); return 2; } buf diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 174cf96720..535fef3876 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -20,7 +20,7 @@ use std::time::Duration; use std::time::Instant; use tokio::time::timeout; -/// This blocks codecs startup, so must be short. +/// This blocks codex startup, so must be short. const CLOUD_REQUIREMENTS_TIMEOUT: Duration = Duration::from_secs(5); #[async_trait] diff --git a/codex-rs/tui/src/bottom_pane/command_popup.rs b/codex-rs/tui/src/bottom_pane/command_popup.rs index 2ece7b4fde..928fb541b3 100644 --- a/codex-rs/tui/src/bottom_pane/command_popup.rs +++ b/codex-rs/tui/src/bottom_pane/command_popup.rs @@ -80,7 +80,7 @@ impl CommandPopup { /// Update the filter string based on the current composer text. The text /// passed in is expected to start with a leading '/'. Everything after the - /// *first* '/" on the *first* line becomes the active filter that is used + /// *first* '/' on the *first* line becomes the active filter that is used /// to narrow down the list of available commands. pub(crate) fn on_composer_text_change(&mut self, text: String) { let first_line = text.lines().next().unwrap_or(""); diff --git a/sdk/typescript/src/thread.ts b/sdk/typescript/src/thread.ts index c36099348c..9ab425e652 100644 --- a/sdk/typescript/src/thread.ts +++ b/sdk/typescript/src/thread.ts @@ -37,7 +37,7 @@ export type UserInput = export type Input = string | UserInput[]; -/** Respesent a thread of conversation with the agent. One thread can have multiple consecutive turns. */ +/** Represent a thread of conversation with the agent. One thread can have multiple consecutive turns. */ export class Thread { private _exec: CodexExec; private _options: CodexOptions; From a8c9e386e723668957013dd137236f630e6d09f1 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Fri, 30 Jan 2026 23:12:25 -0700 Subject: [PATCH 034/139] feat(core) Smart approvals on (#10286) ## Summary Turn on Smart Approvals by default ## Testing - [x] Updated unit tests --- codex-rs/core/src/features.rs | 8 ++------ codex-rs/core/src/tools/spec.rs | 6 +++--- codex-rs/core/tests/suite/client.rs | 2 +- codex-rs/core/tests/suite/collaboration_instructions.rs | 2 +- codex-rs/core/tests/suite/permissions_messages.rs | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 934ab1c0ba..3d351172fe 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -464,12 +464,8 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::RequestRule, key: "request_rule", - stage: Stage::Experimental { - name: "Smart approvals", - menu_description: "Get smarter \"Don't ask again\" rule requests.", - announcement: "NEW: Try Smart approvals to get smarter \"Don't ask again\" requests. Enable in /experimental!", - }, - default_enabled: false, + stage: Stage::Stable, + default_enabled: true, }, FeatureSpec { id: Feature::WindowsSandbox, diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index bf3b7b4b25..a51372b9b3 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -1576,7 +1576,7 @@ mod tests { // Build expected from the same helpers used by the builder. let mut expected: BTreeMap = BTreeMap::from([]); for spec in [ - create_exec_command_tool(false), + create_exec_command_tool(true), create_write_stdin_tool(), create_list_mcp_resources_tool(), create_list_mcp_resource_templates_tool(), @@ -2410,7 +2410,7 @@ mod tests { #[test] fn test_shell_tool() { - let tool = super::create_shell_tool(false); + let tool = super::create_shell_tool(true); let ToolSpec::Function(ResponsesApiTool { description, name, .. }) = &tool @@ -2440,7 +2440,7 @@ Examples of valid command strings: #[test] fn test_shell_command_tool() { - let tool = super::create_shell_command_tool(false); + let tool = super::create_shell_command_tool(true); let ToolSpec::Function(ResponsesApiTool { description, name, .. }) = &tool diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 150513d000..439233d8af 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -320,7 +320,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { .expect("prior assistant message"); let pos_permissions = messages .iter() - .position(|(role, text)| role == "developer" && text.contains("`approval_policy`")) + .position(|(role, text)| role == "developer" && text.contains("")) .expect("permissions message"); let pos_user_instructions = messages .iter() diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 0f492d78c9..7a311b4bf1 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -83,7 +83,7 @@ async fn no_collaboration_instructions_by_default() -> Result<()> { let input = req.single_request().input(); let dev_texts = developer_texts(&input); assert_eq!(dev_texts.len(), 1); - assert!(dev_texts[0].contains("`approval_policy`")); + assert!(dev_texts[0].contains("")); Ok(()) } diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index e4eded15ff..b500689ef6 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -34,7 +34,7 @@ fn permissions_texts(input: &[serde_json::Value]) -> Vec { .first()? .get("text")? .as_str()?; - if text.contains("`approval_policy`") { + if text.contains("") { Some(text.to_string()) } else { None @@ -439,7 +439,7 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { &sandbox_policy, AskForApproval::OnRequest, &Policy::empty(), - false, + true, test.config.cwd.as_path(), ) .into_text(); From 8660ad6c64895fa0a0aea05c3935d9d3828763d7 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Fri, 30 Jan 2026 22:20:02 -0800 Subject: [PATCH 035/139] feat: show runtime metrics in console (#10278) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary of changes: - Adds a new feature flag: runtime_metrics - Declared in core/src/features.rs - Added to core/config.schema.json - Wired into OTEL init in core/src/otel_init.rs - Enables on-demand runtime metric snapshots in OTEL - Adds runtime_metrics: bool to otel/src/config.rs - Enables experimental custom reader features in otel/Cargo.toml - Adds snapshot/reset/summary APIs in: - otel/src/lib.rs - otel/src/metrics/client.rs - otel/src/metrics/config.rs - otel/src/metrics/error.rs - Defines metric names and a runtime summary builder - New files: - otel/src/metrics/names.rs - otel/src/metrics/runtime_metrics.rs - Summarizes totals for: - Tool calls - API requests - SSE/streaming events - Instruments metrics collection in OTEL manager - otel/src/traces/otel_manager.rs now records: - API call counts + durations - SSE event counts + durations (success/failure) - Tool call metrics now use shared constants - Surfaces runtime metrics in the TUI - Resets runtime metrics at turn start in tui/src/chatwidget.rs - Displays metrics in the final separator line in tui/src/history_cell.rs - Adds tests - New OTEL tests: - otel/tests/suite/snapshot.rs - otel/tests/suite/runtime_summary.rs - New TUI test: - final_message_separator_includes_runtime_metrics in tui/src/history_cell.rs Scope: - 19 files changed - ~652 insertions, 38 deletions Screenshot 2026-01-30 at 4 11 34 PM --- codex-rs/core/config.schema.json | 6 + codex-rs/core/src/features.rs | 8 ++ codex-rs/core/src/otel_init.rs | 3 + codex-rs/otel/Cargo.toml | 14 ++- codex-rs/otel/src/config.rs | 1 + codex-rs/otel/src/lib.rs | 38 ++++++ codex-rs/otel/src/metrics/client.rs | 101 ++++++++++++--- codex-rs/otel/src/metrics/config.rs | 9 ++ codex-rs/otel/src/metrics/error.rs | 9 ++ codex-rs/otel/src/metrics/mod.rs | 2 + codex-rs/otel/src/metrics/names.rs | 6 + codex-rs/otel/src/metrics/runtime_metrics.rs | 101 +++++++++++++++ codex-rs/otel/src/otel_provider.rs | 8 +- codex-rs/otel/src/traces/otel_manager.rs | 48 +++++++- codex-rs/otel/tests/suite/mod.rs | 2 + codex-rs/otel/tests/suite/runtime_summary.rs | 77 ++++++++++++ codex-rs/otel/tests/suite/snapshot.rs | 120 ++++++++++++++++++ codex-rs/tui/src/chatwidget.rs | 21 +++- codex-rs/tui/src/history_cell.rs | 123 ++++++++++++++++--- 19 files changed, 659 insertions(+), 38 deletions(-) create mode 100644 codex-rs/otel/src/metrics/names.rs create mode 100644 codex-rs/otel/src/metrics/runtime_metrics.rs create mode 100644 codex-rs/otel/tests/suite/runtime_summary.rs create mode 100644 codex-rs/otel/tests/suite/snapshot.rs diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index cdd2717fd4..e847d13930 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -208,6 +208,9 @@ "responses_websockets": { "type": "boolean" }, + "runtime_metrics": { + "type": "boolean" + }, "shell_snapshot": { "type": "boolean" }, @@ -1233,6 +1236,9 @@ "responses_websockets": { "type": "boolean" }, + "runtime_metrics": { + "type": "boolean" + }, "shell_snapshot": { "type": "boolean" }, diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 3d351172fe..0882b2ae76 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -101,6 +101,8 @@ pub enum Feature { RemoteModels, /// Experimental shell snapshotting. ShellSnapshot, + /// Enable runtime metrics snapshots via a manual reader. + RuntimeMetrics, /// Persist rollout metadata to a local SQLite database. Sqlite, /// Append additional AGENTS.md guidance to user instructions. @@ -437,6 +439,12 @@ pub const FEATURES: &[FeatureSpec] = &[ }, default_enabled: false, }, + FeatureSpec { + id: Feature::RuntimeMetrics, + key: "runtime_metrics", + stage: Stage::UnderDevelopment, + default_enabled: false, + }, FeatureSpec { id: Feature::Sqlite, key: "sqlite", diff --git a/codex-rs/core/src/otel_init.rs b/codex-rs/core/src/otel_init.rs index 9177409b7c..2db80e8d4e 100644 --- a/codex-rs/core/src/otel_init.rs +++ b/codex-rs/core/src/otel_init.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::config::types::OtelExporterKind as Kind; use crate::config::types::OtelHttpProtocol as Protocol; use crate::default_client::originator; +use crate::features::Feature; use codex_otel::config::OtelExporter; use codex_otel::config::OtelHttpProtocol; use codex_otel::config::OtelSettings; @@ -77,6 +78,7 @@ pub fn build_provider( let originator = originator(); let service_name = service_name_override.unwrap_or(originator.value.as_str()); + let runtime_metrics = config.features.enabled(Feature::RuntimeMetrics); OtelProvider::from(&OtelSettings { service_name: service_name.to_string(), @@ -86,6 +88,7 @@ pub fn build_provider( exporter, trace_exporter, metrics_exporter, + runtime_metrics, }) } diff --git a/codex-rs/otel/Cargo.toml b/codex-rs/otel/Cargo.toml index eb19ec7df7..f9a54c53d9 100644 --- a/codex-rs/otel/Cargo.toml +++ b/codex-rs/otel/Cargo.toml @@ -41,7 +41,14 @@ opentelemetry-otlp = { workspace = true, features = [ "tls-roots", ]} opentelemetry-semantic-conventions = { workspace = true } -opentelemetry_sdk = { workspace = true, features = ["logs", "metrics", "rt-tokio", "testing", "trace"] } +opentelemetry_sdk = { workspace = true, features = [ + "experimental_metrics_custom_reader", + "logs", + "metrics", + "rt-tokio", + "testing", + "trace", +] } http = { workspace = true } reqwest = { workspace = true, features = ["blocking", "rustls-tls"] } serde = { workspace = true, features = ["derive"] } @@ -54,5 +61,8 @@ tracing-opentelemetry = { workspace = true } tracing-subscriber = { workspace = true } [dev-dependencies] -opentelemetry_sdk = { workspace = true, features = ["testing"] } +opentelemetry_sdk = { workspace = true, features = [ + "experimental_metrics_custom_reader", + "testing", +] } pretty_assertions = { workspace = true } diff --git a/codex-rs/otel/src/config.rs b/codex-rs/otel/src/config.rs index f8f2d5a106..1898e4bdb0 100644 --- a/codex-rs/otel/src/config.rs +++ b/codex-rs/otel/src/config.rs @@ -37,6 +37,7 @@ pub struct OtelSettings { pub exporter: OtelExporter, pub trace_exporter: OtelExporter, pub metrics_exporter: OtelExporter, + pub runtime_metrics: bool, } #[derive(Clone, Debug)] diff --git a/codex-rs/otel/src/lib.rs b/codex-rs/otel/src/lib.rs index 93975cbf9d..aed9e15b8e 100644 --- a/codex-rs/otel/src/lib.rs +++ b/codex-rs/otel/src/lib.rs @@ -14,9 +14,14 @@ use crate::metrics::validation::validate_tag_key; use crate::metrics::validation::validate_tag_value; use crate::otel_provider::OtelProvider; use codex_protocol::ThreadId; +use opentelemetry_sdk::metrics::data::ResourceMetrics; use serde::Serialize; use std::time::Duration; use strum_macros::Display; +use tracing::debug; + +pub use crate::metrics::runtime_metrics::RuntimeMetricTotals; +pub use crate::metrics::runtime_metrics::RuntimeMetricsSummary; #[derive(Debug, Clone, Serialize, Display)] #[serde(rename_all = "snake_case")] @@ -137,6 +142,39 @@ impl OtelManager { metrics.shutdown() } + pub fn snapshot_metrics(&self) -> MetricsResult { + let Some(metrics) = &self.metrics else { + return Err(MetricsError::ExporterDisabled); + }; + metrics.snapshot() + } + + /// Collect and discard a runtime metrics snapshot to reset delta accumulators. + pub fn reset_runtime_metrics(&self) { + if self.metrics.is_none() { + return; + } + if let Err(err) = self.snapshot_metrics() { + debug!("runtime metrics reset skipped: {err}"); + } + } + + /// Collect a runtime metrics summary if debug snapshots are available. + pub fn runtime_metrics_summary(&self) -> Option { + let snapshot = match self.snapshot_metrics() { + Ok(snapshot) => snapshot, + Err(_) => { + return None; + } + }; + let summary = RuntimeMetricsSummary::from_snapshot(&snapshot); + if summary.is_empty() { + None + } else { + Some(summary) + } + } + fn tags_with_metadata<'a>( &'a self, tags: &'a [(&'a str, &'a str)], diff --git a/codex-rs/otel/src/metrics/client.rs b/codex-rs/otel/src/metrics/client.rs index 62a2df2e8a..386f811422 100644 --- a/codex-rs/otel/src/metrics/client.rs +++ b/codex-rs/otel/src/metrics/client.rs @@ -22,13 +22,20 @@ use opentelemetry_otlp::WithTonicConfig; use opentelemetry_otlp::tonic_types::metadata::MetadataMap; use opentelemetry_otlp::tonic_types::transport::ClientTlsConfig; use opentelemetry_sdk::Resource; +use opentelemetry_sdk::metrics::InstrumentKind; +use opentelemetry_sdk::metrics::ManualReader; use opentelemetry_sdk::metrics::PeriodicReader; +use opentelemetry_sdk::metrics::Pipeline; use opentelemetry_sdk::metrics::SdkMeterProvider; use opentelemetry_sdk::metrics::Temporality; +use opentelemetry_sdk::metrics::data::ResourceMetrics; +use opentelemetry_sdk::metrics::reader::MetricReader; use opentelemetry_semantic_conventions as semconv; use std::collections::BTreeMap; use std::collections::HashMap; +use std::sync::Arc; use std::sync::Mutex; +use std::sync::Weak; use std::time::Duration; use tracing::debug; @@ -37,6 +44,39 @@ const METER_NAME: &str = "codex"; const DURATION_UNIT: &str = "ms"; const DURATION_DESCRIPTION: &str = "Duration in milliseconds."; +#[derive(Clone, Debug)] +struct SharedManualReader { + inner: Arc, +} + +impl SharedManualReader { + fn new(inner: Arc) -> Self { + Self { inner } + } +} + +impl MetricReader for SharedManualReader { + fn register_pipeline(&self, pipeline: Weak) { + self.inner.register_pipeline(pipeline); + } + + fn collect(&self, rm: &mut ResourceMetrics) -> opentelemetry_sdk::error::OTelSdkResult { + self.inner.collect(rm) + } + + fn force_flush(&self) -> opentelemetry_sdk::error::OTelSdkResult { + self.inner.force_flush() + } + + fn shutdown_with_timeout(&self, timeout: Duration) -> opentelemetry_sdk::error::OTelSdkResult { + self.inner.shutdown_with_timeout(timeout) + } + + fn temporality(&self, kind: InstrumentKind) -> Temporality { + self.inner.temporality(kind) + } +} + #[derive(Debug)] struct MetricsClientInner { meter_provider: SdkMeterProvider, @@ -44,6 +84,7 @@ struct MetricsClientInner { counters: Mutex>>, histograms: Mutex>>, duration_histograms: Mutex>>, + runtime_reader: Option>, default_tags: BTreeMap, } @@ -144,26 +185,41 @@ pub struct MetricsClient(std::sync::Arc); impl MetricsClient { /// Build a metrics client from configuration and validate defaults. pub fn new(config: MetricsConfig) -> Result { - validate_tags(&config.default_tags)?; + let MetricsConfig { + environment, + service_name, + service_version, + exporter, + export_interval, + runtime_reader, + default_tags, + } = config; + + validate_tags(&default_tags)?; let resource = Resource::builder() - .with_service_name(config.service_name.clone()) + .with_service_name(service_name) .with_attributes(vec![ - KeyValue::new( - semconv::attribute::SERVICE_VERSION, - config.service_version.clone(), - ), - KeyValue::new(ENV_ATTRIBUTE, config.environment.clone()), + KeyValue::new(semconv::attribute::SERVICE_VERSION, service_version), + KeyValue::new(ENV_ATTRIBUTE, environment), ]) .build(); - let (meter_provider, meter) = match config.exporter { + let runtime_reader = runtime_reader.then(|| { + Arc::new( + ManualReader::builder() + .with_temporality(Temporality::Delta) + .build(), + ) + }); + + let (meter_provider, meter) = match exporter { MetricsExporter::InMemory(exporter) => { - build_provider(resource, exporter, config.export_interval) + build_provider(resource, exporter, export_interval, runtime_reader.clone()) } MetricsExporter::Otlp(exporter) => { let exporter = build_otlp_metric_exporter(exporter, Temporality::Delta)?; - build_provider(resource, exporter, config.export_interval) + build_provider(resource, exporter, export_interval, runtime_reader.clone()) } }; @@ -173,7 +229,8 @@ impl MetricsClient { counters: Mutex::new(HashMap::new()), histograms: Mutex::new(HashMap::new()), duration_histograms: Mutex::new(HashMap::new()), - default_tags: config.default_tags, + runtime_reader, + default_tags, }))) } @@ -209,6 +266,18 @@ impl MetricsClient { Ok(Timer::new(name, tags, self)) } + /// Collect a runtime metrics snapshot without shutting down the provider. + pub fn snapshot(&self) -> Result { + let Some(reader) = &self.0.runtime_reader else { + return Err(MetricsError::RuntimeSnapshotUnavailable); + }; + let mut snapshot = ResourceMetrics::default(); + reader + .collect(&mut snapshot) + .map_err(|source| MetricsError::RuntimeSnapshotCollect { source })?; + Ok(snapshot) + } + /// Flush metrics and stop the underlying OTEL meter provider. pub fn shutdown(&self) -> Result<()> { self.0.shutdown() @@ -219,6 +288,7 @@ fn build_provider( resource: Resource, exporter: E, interval: Option, + runtime_reader: Option>, ) -> (SdkMeterProvider, Meter) where E: opentelemetry_sdk::metrics::exporter::PushMetricExporter + 'static, @@ -228,10 +298,11 @@ where reader_builder = reader_builder.with_interval(interval); } let reader = reader_builder.build(); - let provider = SdkMeterProvider::builder() - .with_resource(resource) - .with_reader(reader) - .build(); + let mut provider_builder = SdkMeterProvider::builder().with_resource(resource); + if let Some(reader) = runtime_reader { + provider_builder = provider_builder.with_reader(SharedManualReader::new(reader)); + } + let provider = provider_builder.with_reader(reader).build(); let meter = provider.meter(METER_NAME); (provider, meter) } diff --git a/codex-rs/otel/src/metrics/config.rs b/codex-rs/otel/src/metrics/config.rs index c7a459183b..dfe6d83bfe 100644 --- a/codex-rs/otel/src/metrics/config.rs +++ b/codex-rs/otel/src/metrics/config.rs @@ -19,6 +19,7 @@ pub struct MetricsConfig { pub(crate) service_version: String, pub(crate) exporter: MetricsExporter, pub(crate) export_interval: Option, + pub(crate) runtime_reader: bool, pub(crate) default_tags: BTreeMap, } @@ -35,6 +36,7 @@ impl MetricsConfig { service_version: service_version.into(), exporter: MetricsExporter::Otlp(exporter), export_interval: None, + runtime_reader: false, default_tags: BTreeMap::new(), } } @@ -52,6 +54,7 @@ impl MetricsConfig { service_version: service_version.into(), exporter: MetricsExporter::InMemory(exporter), export_interval: None, + runtime_reader: false, default_tags: BTreeMap::new(), } } @@ -62,6 +65,12 @@ impl MetricsConfig { self } + /// Enable a manual reader for on-demand runtime snapshots. + pub fn with_runtime_reader(mut self) -> Self { + self.runtime_reader = true; + self + } + /// Add a default tag that will be sent with every metric. pub fn with_tag(mut self, key: impl Into, value: impl Into) -> Result { let key = key.into(); diff --git a/codex-rs/otel/src/metrics/error.rs b/codex-rs/otel/src/metrics/error.rs index dfb9653254..db6aba157a 100644 --- a/codex-rs/otel/src/metrics/error.rs +++ b/codex-rs/otel/src/metrics/error.rs @@ -34,4 +34,13 @@ pub enum MetricsError { #[source] source: opentelemetry_sdk::error::OTelSdkError, }, + + #[error("runtime metrics snapshot reader is not enabled")] + RuntimeSnapshotUnavailable, + + #[error("failed to collect runtime metrics snapshot from metrics reader")] + RuntimeSnapshotCollect { + #[source] + source: opentelemetry_sdk::error::OTelSdkError, + }, } diff --git a/codex-rs/otel/src/metrics/mod.rs b/codex-rs/otel/src/metrics/mod.rs index 30a7aa1cf6..06b06e8d26 100644 --- a/codex-rs/otel/src/metrics/mod.rs +++ b/codex-rs/otel/src/metrics/mod.rs @@ -1,6 +1,8 @@ mod client; mod config; mod error; +pub(crate) mod names; +pub(crate) mod runtime_metrics; pub(crate) mod timer; pub(crate) mod validation; diff --git a/codex-rs/otel/src/metrics/names.rs b/codex-rs/otel/src/metrics/names.rs new file mode 100644 index 0000000000..4db5dfb028 --- /dev/null +++ b/codex-rs/otel/src/metrics/names.rs @@ -0,0 +1,6 @@ +pub(crate) const TOOL_CALL_COUNT_METRIC: &str = "codex.tool.call"; +pub(crate) const TOOL_CALL_DURATION_METRIC: &str = "codex.tool.call.duration_ms"; +pub(crate) const API_CALL_COUNT_METRIC: &str = "codex.api_request"; +pub(crate) const API_CALL_DURATION_METRIC: &str = "codex.api_request.duration_ms"; +pub(crate) const SSE_EVENT_COUNT_METRIC: &str = "codex.sse_event"; +pub(crate) const SSE_EVENT_DURATION_METRIC: &str = "codex.sse_event.duration_ms"; diff --git a/codex-rs/otel/src/metrics/runtime_metrics.rs b/codex-rs/otel/src/metrics/runtime_metrics.rs new file mode 100644 index 0000000000..98498a3532 --- /dev/null +++ b/codex-rs/otel/src/metrics/runtime_metrics.rs @@ -0,0 +1,101 @@ +use crate::metrics::names::API_CALL_COUNT_METRIC; +use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::SSE_EVENT_COUNT_METRIC; +use crate::metrics::names::SSE_EVENT_DURATION_METRIC; +use crate::metrics::names::TOOL_CALL_COUNT_METRIC; +use crate::metrics::names::TOOL_CALL_DURATION_METRIC; +use opentelemetry_sdk::metrics::data::AggregatedMetrics; +use opentelemetry_sdk::metrics::data::Metric; +use opentelemetry_sdk::metrics::data::MetricData; +use opentelemetry_sdk::metrics::data::ResourceMetrics; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct RuntimeMetricTotals { + pub count: u64, + pub duration_ms: u64, +} + +impl RuntimeMetricTotals { + pub fn is_empty(self) -> bool { + self.count == 0 && self.duration_ms == 0 + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct RuntimeMetricsSummary { + pub tool_calls: RuntimeMetricTotals, + pub api_calls: RuntimeMetricTotals, + pub streaming_events: RuntimeMetricTotals, +} + +impl RuntimeMetricsSummary { + pub fn is_empty(self) -> bool { + self.tool_calls.is_empty() && self.api_calls.is_empty() && self.streaming_events.is_empty() + } + + pub(crate) fn from_snapshot(snapshot: &ResourceMetrics) -> Self { + let tool_calls = RuntimeMetricTotals { + count: sum_counter(snapshot, TOOL_CALL_COUNT_METRIC), + duration_ms: sum_histogram_ms(snapshot, TOOL_CALL_DURATION_METRIC), + }; + let api_calls = RuntimeMetricTotals { + count: sum_counter(snapshot, API_CALL_COUNT_METRIC), + duration_ms: sum_histogram_ms(snapshot, API_CALL_DURATION_METRIC), + }; + let streaming_events = RuntimeMetricTotals { + count: sum_counter(snapshot, SSE_EVENT_COUNT_METRIC), + duration_ms: sum_histogram_ms(snapshot, SSE_EVENT_DURATION_METRIC), + }; + Self { + tool_calls, + api_calls, + streaming_events, + } + } +} + +fn sum_counter(snapshot: &ResourceMetrics, name: &str) -> u64 { + snapshot + .scope_metrics() + .flat_map(opentelemetry_sdk::metrics::data::ScopeMetrics::metrics) + .filter(|metric| metric.name() == name) + .map(sum_counter_metric) + .sum() +} + +fn sum_counter_metric(metric: &Metric) -> u64 { + match metric.data() { + AggregatedMetrics::U64(MetricData::Sum(sum)) => sum + .data_points() + .map(opentelemetry_sdk::metrics::data::SumDataPoint::value) + .sum(), + _ => 0, + } +} + +fn sum_histogram_ms(snapshot: &ResourceMetrics, name: &str) -> u64 { + snapshot + .scope_metrics() + .flat_map(opentelemetry_sdk::metrics::data::ScopeMetrics::metrics) + .filter(|metric| metric.name() == name) + .map(sum_histogram_metric_ms) + .sum() +} + +fn sum_histogram_metric_ms(metric: &Metric) -> u64 { + match metric.data() { + AggregatedMetrics::F64(MetricData::Histogram(histogram)) => histogram + .data_points() + .map(|point| f64_to_u64(point.sum())) + .sum(), + _ => 0, + } +} + +fn f64_to_u64(value: f64) -> u64 { + if !value.is_finite() || value <= 0.0 { + return 0; + } + let clamped = value.min(u64::MAX as f64); + clamped.round() as u64 +} diff --git a/codex-rs/otel/src/otel_provider.rs b/codex-rs/otel/src/otel_provider.rs index 8ad264f8a7..b1ea099fa2 100644 --- a/codex-rs/otel/src/otel_provider.rs +++ b/codex-rs/otel/src/otel_provider.rs @@ -75,12 +75,16 @@ impl OtelProvider { let metrics = if matches!(metric_exporter, OtelExporter::None) { None } else { - Some(MetricsClient::new(MetricsConfig::otlp( + let mut config = MetricsConfig::otlp( settings.environment.clone(), settings.service_name.clone(), settings.service_version.clone(), metric_exporter, - ))?) + ); + if settings.runtime_metrics { + config = config.with_runtime_reader(); + } + Some(MetricsClient::new(config)?) }; if let Some(metrics) = metrics.as_ref() { diff --git a/codex-rs/otel/src/traces/otel_manager.rs b/codex-rs/otel/src/traces/otel_manager.rs index 7ce411970c..9a7744ee60 100644 --- a/codex-rs/otel/src/traces/otel_manager.rs +++ b/codex-rs/otel/src/traces/otel_manager.rs @@ -1,3 +1,9 @@ +use crate::metrics::names::API_CALL_COUNT_METRIC; +use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::SSE_EVENT_COUNT_METRIC; +use crate::metrics::names::SSE_EVENT_DURATION_METRIC; +use crate::metrics::names::TOOL_CALL_COUNT_METRIC; +use crate::metrics::names::TOOL_CALL_DURATION_METRIC; use crate::otel_provider::traceparent_context_from_env; use chrono::SecondsFormat; use chrono::Utc; @@ -29,6 +35,8 @@ pub use crate::OtelEventMetadata; pub use crate::OtelManager; pub use crate::ToolDecisionSource; +const SSE_UNKNOWN_KIND: &str = "unknown"; + impl OtelManager { #[allow(clippy::too_many_arguments)] pub fn new( @@ -148,6 +156,21 @@ impl OtelManager { error: Option<&str>, duration: Duration, ) { + let success = status.is_some_and(|code| (200..=299).contains(&code)) && error.is_none(); + let success_str = if success { "true" } else { "false" }; + let status_str = status + .map(|code| code.to_string()) + .unwrap_or_else(|| "none".to_string()); + self.counter( + API_CALL_COUNT_METRIC, + 1, + &[("status", status_str.as_str()), ("success", success_str)], + ); + self.record_duration( + API_CALL_DURATION_METRIC, + duration, + &[("status", status_str.as_str()), ("success", success_str)], + ); tracing::event!( tracing::Level::INFO, event.name = "codex.api_request", @@ -215,6 +238,16 @@ impl OtelManager { } fn sse_event(&self, kind: &str, duration: Duration) { + self.counter( + SSE_EVENT_COUNT_METRIC, + 1, + &[("kind", kind), ("success", "true")], + ); + self.record_duration( + SSE_EVENT_DURATION_METRIC, + duration, + &[("kind", kind), ("success", "true")], + ); tracing::event!( tracing::Level::INFO, event.name = "codex.sse_event", @@ -236,6 +269,17 @@ impl OtelManager { where T: Display, { + let kind_str = kind.map_or(SSE_UNKNOWN_KIND, String::as_str); + self.counter( + SSE_EVENT_COUNT_METRIC, + 1, + &[("kind", kind_str), ("success", "false")], + ); + self.record_duration( + SSE_EVENT_DURATION_METRIC, + duration, + &[("kind", kind_str), ("success", "false")], + ); match kind { Some(kind) => tracing::event!( tracing::Level::INFO, @@ -443,12 +487,12 @@ impl OtelManager { ) { let success_str = if success { "true" } else { "false" }; self.counter( - "codex.tool.call", + TOOL_CALL_COUNT_METRIC, 1, &[("tool", tool_name), ("success", success_str)], ); self.record_duration( - "codex.tool.call.duration_ms", + TOOL_CALL_DURATION_METRIC, duration, &[("tool", tool_name), ("success", success_str)], ); diff --git a/codex-rs/otel/tests/suite/mod.rs b/codex-rs/otel/tests/suite/mod.rs index c79c7e37c4..16aa0f4942 100644 --- a/codex-rs/otel/tests/suite/mod.rs +++ b/codex-rs/otel/tests/suite/mod.rs @@ -1,5 +1,7 @@ mod manager_metrics; mod otlp_http_loopback; +mod runtime_summary; mod send; +mod snapshot; mod timing; mod validation; diff --git a/codex-rs/otel/tests/suite/runtime_summary.rs b/codex-rs/otel/tests/suite/runtime_summary.rs new file mode 100644 index 0000000000..d51c40a9ed --- /dev/null +++ b/codex-rs/otel/tests/suite/runtime_summary.rs @@ -0,0 +1,77 @@ +use codex_app_server_protocol::AuthMode; +use codex_otel::OtelManager; +use codex_otel::RuntimeMetricTotals; +use codex_otel::RuntimeMetricsSummary; +use codex_otel::metrics::MetricsClient; +use codex_otel::metrics::MetricsConfig; +use codex_otel::metrics::Result; +use codex_protocol::ThreadId; +use codex_protocol::protocol::SessionSource; +use eventsource_stream::Event as StreamEvent; +use opentelemetry_sdk::metrics::InMemoryMetricExporter; +use pretty_assertions::assert_eq; +use std::time::Duration; + +#[test] +fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<()> { + let exporter = InMemoryMetricExporter::default(); + let metrics = MetricsClient::new( + MetricsConfig::in_memory("test", "codex-cli", env!("CARGO_PKG_VERSION"), exporter) + .with_runtime_reader(), + )?; + let manager = OtelManager::new( + ThreadId::new(), + "gpt-5.1", + "gpt-5.1", + Some("account-id".to_string()), + None, + Some(AuthMode::ApiKey), + true, + "tty".to_string(), + SessionSource::Cli, + ) + .with_metrics(metrics); + + manager.reset_runtime_metrics(); + + manager.tool_result( + "shell", + "call-1", + "{\"cmd\":\"echo\"}", + Duration::from_millis(250), + true, + "ok", + ); + manager.record_api_request(1, Some(200), None, Duration::from_millis(300)); + let sse_response: std::result::Result< + Option>>, + tokio::time::error::Elapsed, + > = Ok(Some(Ok(StreamEvent { + event: "response.created".to_string(), + data: "{}".to_string(), + id: String::new(), + retry: None, + }))); + manager.log_sse_event(&sse_response, Duration::from_millis(120)); + + let summary = manager + .runtime_metrics_summary() + .expect("runtime metrics summary should be available"); + let expected = RuntimeMetricsSummary { + tool_calls: RuntimeMetricTotals { + count: 1, + duration_ms: 250, + }, + api_calls: RuntimeMetricTotals { + count: 1, + duration_ms: 300, + }, + streaming_events: RuntimeMetricTotals { + count: 1, + duration_ms: 120, + }, + }; + assert_eq!(summary, expected); + + Ok(()) +} diff --git a/codex-rs/otel/tests/suite/snapshot.rs b/codex-rs/otel/tests/suite/snapshot.rs new file mode 100644 index 0000000000..f0a7a18c54 --- /dev/null +++ b/codex-rs/otel/tests/suite/snapshot.rs @@ -0,0 +1,120 @@ +use crate::harness::attributes_to_map; +use crate::harness::find_metric; +use codex_app_server_protocol::AuthMode; +use codex_otel::OtelManager; +use codex_otel::metrics::MetricsClient; +use codex_otel::metrics::MetricsConfig; +use codex_otel::metrics::Result; +use codex_protocol::ThreadId; +use codex_protocol::protocol::SessionSource; +use opentelemetry_sdk::metrics::InMemoryMetricExporter; +use opentelemetry_sdk::metrics::data::AggregatedMetrics; +use opentelemetry_sdk::metrics::data::MetricData; +use pretty_assertions::assert_eq; +use std::collections::BTreeMap; + +#[test] +fn snapshot_collects_metrics_without_shutdown() -> Result<()> { + let exporter = InMemoryMetricExporter::default(); + let config = MetricsConfig::in_memory( + "test", + "codex-cli", + env!("CARGO_PKG_VERSION"), + exporter.clone(), + ) + .with_tag("service", "codex-cli")? + .with_runtime_reader(); + let metrics = MetricsClient::new(config)?; + + metrics.counter( + "codex.tool.call", + 1, + &[("tool", "shell"), ("success", "true")], + )?; + + let snapshot = metrics.snapshot()?; + + let metric = find_metric(&snapshot, "codex.tool.call").expect("counter metric missing"); + let attrs = match metric.data() { + AggregatedMetrics::U64(data) => match data { + MetricData::Sum(sum) => { + let points: Vec<_> = sum.data_points().collect(); + assert_eq!(points.len(), 1); + attributes_to_map(points[0].attributes()) + } + _ => panic!("unexpected counter aggregation"), + }, + _ => panic!("unexpected counter data type"), + }; + + let expected = BTreeMap::from([ + ("service".to_string(), "codex-cli".to_string()), + ("success".to_string(), "true".to_string()), + ("tool".to_string(), "shell".to_string()), + ]); + assert_eq!(attrs, expected); + + let finished = exporter + .get_finished_metrics() + .expect("finished metrics should be readable"); + assert!(finished.is_empty(), "expected no periodic exports yet"); + + Ok(()) +} + +#[test] +fn manager_snapshot_metrics_collects_without_shutdown() -> Result<()> { + let exporter = InMemoryMetricExporter::default(); + let config = MetricsConfig::in_memory("test", "codex-cli", env!("CARGO_PKG_VERSION"), exporter) + .with_tag("service", "codex-cli")? + .with_runtime_reader(); + let metrics = MetricsClient::new(config)?; + let manager = OtelManager::new( + ThreadId::new(), + "gpt-5.1", + "gpt-5.1", + Some("account-id".to_string()), + None, + Some(AuthMode::ApiKey), + true, + "tty".to_string(), + SessionSource::Cli, + ) + .with_metrics(metrics); + + manager.counter( + "codex.tool.call", + 1, + &[("tool", "shell"), ("success", "true")], + ); + + let snapshot = manager.snapshot_metrics()?; + let metric = find_metric(&snapshot, "codex.tool.call").expect("counter metric missing"); + let attrs = match metric.data() { + AggregatedMetrics::U64(data) => match data { + MetricData::Sum(sum) => { + let points: Vec<_> = sum.data_points().collect(); + assert_eq!(points.len(), 1); + attributes_to_map(points[0].attributes()) + } + _ => panic!("unexpected counter aggregation"), + }, + _ => panic!("unexpected counter data type"), + }; + + let expected = BTreeMap::from([ + ( + "app.version".to_string(), + env!("CARGO_PKG_VERSION").to_string(), + ), + ("auth_mode".to_string(), AuthMode::ApiKey.to_string()), + ("model".to_string(), "gpt-5.1".to_string()), + ("service".to_string(), "codex-cli".to_string()), + ("session_source".to_string(), "cli".to_string()), + ("success".to_string(), "true".to_string()), + ("tool".to_string(), "shell".to_string()), + ]); + assert_eq!(attrs, expected); + + Ok(()) +} diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index b0c8c7cf33..1efd6b8e44 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1017,6 +1017,7 @@ impl ChatWidget { self.plan_delta_buffer.clear(); self.plan_item_active = false; self.plan_stream_controller = None; + self.otel_manager.reset_runtime_metrics(); self.bottom_pane.clear_quit_shortcut_hint(); self.quit_shortcut_expires_at = None; self.quit_shortcut_key = None; @@ -1038,6 +1039,21 @@ impl ChatWidget { self.add_boxed_history(cell); } self.flush_unified_exec_wait_streak(); + if !from_replay { + let runtime_metrics = self.otel_manager.runtime_metrics_summary(); + if runtime_metrics.is_some() { + let elapsed_seconds = self + .bottom_pane + .status_widget() + .map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds); + self.add_to_history(history_cell::FinalMessageSeparator::new( + elapsed_seconds, + runtime_metrics, + )); + } + self.needs_final_message_separator = false; + self.had_work_activity = false; + } // Mark task stopped and request redraw now that all content is in history. self.agent_turn_running = false; self.update_task_running_state(); @@ -1887,7 +1903,10 @@ impl ChatWidget { .status_widget() .map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds) .map(|current| self.worked_elapsed_from(current)); - self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds)); + self.add_to_history(history_cell::FinalMessageSeparator::new( + elapsed_seconds, + None, + )); self.needs_final_message_separator = false; self.had_work_activity = false; } else if self.needs_final_message_separator { diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 74fc650908..826624889e 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -45,6 +45,7 @@ use codex_core::protocol::McpAuthStatus; use codex_core::protocol::McpInvocation; use codex_core::protocol::SessionConfiguredEvent; use codex_core::web_search::web_search_detail; +use codex_otel::RuntimeMetricsSummary; use codex_protocol::models::WebSearchAction; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::PlanItemArg; @@ -1966,34 +1967,96 @@ pub(crate) fn new_reasoning_summary_block(full_reasoning_buffer: String) -> Box< /// divider. pub struct FinalMessageSeparator { elapsed_seconds: Option, + runtime_metrics: Option, } impl FinalMessageSeparator { /// Creates a separator; `elapsed_seconds` typically comes from the status indicator timer. - pub(crate) fn new(elapsed_seconds: Option) -> Self { - Self { elapsed_seconds } + pub(crate) fn new( + elapsed_seconds: Option, + runtime_metrics: Option, + ) -> Self { + Self { + elapsed_seconds, + runtime_metrics, + } } } impl HistoryCell for FinalMessageSeparator { fn display_lines(&self, width: u16) -> Vec> { - let elapsed_seconds = self + let mut label_parts = Vec::new(); + if let Some(elapsed_seconds) = self .elapsed_seconds - .map(super::status_indicator_widget::fmt_elapsed_compact); - if let Some(elapsed_seconds) = elapsed_seconds { - let worked_for = format!("─ Worked for {elapsed_seconds} ─"); - let worked_for_width = worked_for.width(); - vec![ - Line::from_iter([ - worked_for, - "─".repeat((width as usize).saturating_sub(worked_for_width)), - ]) - .dim(), - ] - } else { - vec![Line::from_iter(["─".repeat(width as usize).dim()])] + .map(super::status_indicator_widget::fmt_elapsed_compact) + { + label_parts.push(format!("Worked for {elapsed_seconds}")); } + if let Some(metrics_label) = self.runtime_metrics.and_then(runtime_metrics_label) { + label_parts.push(metrics_label); + } + + if label_parts.is_empty() { + return vec![Line::from_iter(["─".repeat(width as usize).dim()])]; + } + + let label = format!("─ {} ─", label_parts.join(" • ")); + let (label, _suffix, label_width) = take_prefix_by_width(&label, width as usize); + vec![ + Line::from_iter([ + label, + "─".repeat((width as usize).saturating_sub(label_width)), + ]) + .dim(), + ] } } +fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { + let mut parts = Vec::new(); + if summary.tool_calls.count > 0 { + let duration = format_duration_ms(summary.tool_calls.duration_ms); + let calls = pluralize(summary.tool_calls.count, "call", "calls"); + parts.push(format!( + "Local tools: {} {calls} ({duration})", + summary.tool_calls.count + )); + } + if summary.api_calls.count > 0 { + let duration = format_duration_ms(summary.api_calls.duration_ms); + let calls = pluralize(summary.api_calls.count, "call", "calls"); + parts.push(format!( + "Inference: {} {calls} ({duration})", + summary.api_calls.count + )); + } + if summary.streaming_events.count > 0 { + let duration = format_duration_ms(summary.streaming_events.duration_ms); + let stream_label = pluralize(summary.streaming_events.count, "Stream", "Streams"); + let events = pluralize(summary.streaming_events.count, "event", "events"); + parts.push(format!( + "{stream_label}: {} {events} ({duration})", + summary.streaming_events.count + )); + } + if parts.is_empty() { + None + } else { + Some(parts.join(" • ")) + } +} + +fn format_duration_ms(duration_ms: u64) -> String { + if duration_ms >= 1_000 { + let seconds = duration_ms as f64 / 1_000.0; + format!("{seconds:.1}s") + } else { + format!("{duration_ms}ms") + } +} + +fn pluralize(count: u64, singular: &'static str, plural: &'static str) -> &'static str { + if count == 1 { singular } else { plural } +} + fn format_mcp_invocation<'a>(invocation: McpInvocation) -> Line<'a> { let args_str = invocation .arguments @@ -2026,6 +2089,8 @@ mod tests { use codex_core::config::types::McpServerConfig; use codex_core::config::types::McpServerTransportConfig; use codex_core::protocol::McpAuthStatus; + use codex_otel::RuntimeMetricTotals; + use codex_otel::RuntimeMetricsSummary; use codex_protocol::models::WebSearchAction; use codex_protocol::parse_command::ParsedCommand; use dirs::home_dir; @@ -2101,6 +2166,32 @@ mod tests { ); } + #[test] + fn final_message_separator_includes_runtime_metrics() { + let summary = RuntimeMetricsSummary { + tool_calls: RuntimeMetricTotals { + count: 3, + duration_ms: 2_450, + }, + api_calls: RuntimeMetricTotals { + count: 2, + duration_ms: 1_200, + }, + streaming_events: RuntimeMetricTotals { + count: 6, + duration_ms: 900, + }, + }; + let cell = FinalMessageSeparator::new(Some(12), Some(summary)); + let rendered = render_lines(&cell.display_lines(120)); + + assert_eq!(rendered.len(), 1); + assert!(rendered[0].contains("Worked for 12s")); + assert!(rendered[0].contains("Local tools: 3 calls (2.5s)")); + assert!(rendered[0].contains("Inference: 2 calls (1.2s)")); + assert!(rendered[0].contains("Streams: 6 events (900ms)")); + } + #[test] fn ps_output_empty_snapshot() { let cell = new_unified_exec_processes_output(Vec::new()); From 2a299317d208f8d41a62b9350e4c5eeb124e60d2 Mon Sep 17 00:00:00 2001 From: willwang-openai Date: Sun, 1 Feb 2026 01:13:25 +0900 Subject: [PATCH 036/139] display promo message in usage error (#10285) If a promo message is attached to a rate limit response, then display it in the error message. --- codex-rs/codex-api/src/rate_limits.rs | 8 ++++ codex-rs/core/src/api_bridge.rs | 3 ++ codex-rs/core/src/error.rs | 56 +++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/codex-rs/codex-api/src/rate_limits.rs b/codex-rs/codex-api/src/rate_limits.rs index bb8ede2f57..c29aab21f8 100644 --- a/codex-rs/codex-api/src/rate_limits.rs +++ b/codex-rs/codex-api/src/rate_limits.rs @@ -41,6 +41,14 @@ pub fn parse_rate_limit(headers: &HeaderMap) -> Option { }) } +/// Parses the bespoke Codex rate-limit headers into a `RateLimitSnapshot`. +pub fn parse_promo_message(headers: &HeaderMap) -> Option { + parse_header_str(headers, "x-codex-promo-message") + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(std::string::ToString::to_string) +} + fn parse_rate_limit_window( headers: &HeaderMap, used_percent_header: &str, diff --git a/codex-rs/core/src/api_bridge.rs b/codex-rs/core/src/api_bridge.rs index ec21f1ec85..86aeaedda0 100644 --- a/codex-rs/core/src/api_bridge.rs +++ b/codex-rs/core/src/api_bridge.rs @@ -3,6 +3,7 @@ use chrono::Utc; use codex_api::AuthProvider as ApiAuthProvider; use codex_api::TransportError; use codex_api::error::ApiError; +use codex_api::rate_limits::parse_promo_message; use codex_api::rate_limits::parse_rate_limit; use http::HeaderMap; use serde::Deserialize; @@ -70,6 +71,7 @@ pub(crate) fn map_api_error(err: ApiError) -> CodexErr { if let Ok(err) = serde_json::from_str::(&body_text) { if err.error.error_type.as_deref() == Some("usage_limit_reached") { let rate_limits = headers.as_ref().and_then(parse_rate_limit); + let promo_message = headers.as_ref().and_then(parse_promo_message); let resets_at = err .error .resets_at @@ -78,6 +80,7 @@ pub(crate) fn map_api_error(err: ApiError) -> CodexErr { plan_type: err.error.plan_type, resets_at, rate_limits, + promo_message, }); } else if err.error.error_type.as_deref() == Some("usage_not_included") { return CodexErr::UsageNotIncluded; diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index f9f896c3b8..684d968162 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -126,7 +126,7 @@ pub enum CodexErr { QuotaExceeded, #[error( - "To use Codex with your ChatGPT plan, upgrade to Plus: https://openai.com/chatgpt/pricing." + "To use Codex with your ChatGPT plan, upgrade to Plus: https://chatgpt.com/explore/plus." )] UsageNotIncluded, @@ -360,13 +360,22 @@ pub struct UsageLimitReachedError { pub(crate) plan_type: Option, pub(crate) resets_at: Option>, pub(crate) rate_limits: Option, + pub(crate) promo_message: Option, } impl std::fmt::Display for UsageLimitReachedError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(promo_message) = &self.promo_message { + return write!( + f, + "You've hit your usage limit. {promo_message},{}", + retry_suffix_after_or(self.resets_at.as_ref()) + ); + } + let message = match self.plan_type.as_ref() { Some(PlanType::Known(KnownPlan::Plus)) => format!( - "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit https://chatgpt.com/codex/settings/usage to purchase more credits{}", + "You've hit your usage limit. Upgrade to Pro (https://chatgpt.com/explore/pro), visit https://chatgpt.com/codex/settings/usage to purchase more credits{}", retry_suffix_after_or(self.resets_at.as_ref()) ), Some(PlanType::Known(KnownPlan::Team)) | Some(PlanType::Known(KnownPlan::Business)) => { @@ -377,7 +386,7 @@ impl std::fmt::Display for UsageLimitReachedError { } Some(PlanType::Known(KnownPlan::Free)) | Some(PlanType::Known(KnownPlan::Go)) => { format!( - "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing),{}", + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://chatgpt.com/explore/plus),{}", retry_suffix_after_or(self.resets_at.as_ref()) ) } @@ -670,10 +679,11 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Plus)), resets_at: None, rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; assert_eq!( err.to_string(), - "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again later." + "You've hit your usage limit. Upgrade to Pro (https://chatgpt.com/explore/pro), visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again later." ); } @@ -816,10 +826,11 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Free)), resets_at: None, rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; assert_eq!( err.to_string(), - "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing), or try again later." + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://chatgpt.com/explore/plus), or try again later." ); } @@ -829,10 +840,11 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Go)), resets_at: None, rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; assert_eq!( err.to_string(), - "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing), or try again later." + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://chatgpt.com/explore/plus), or try again later." ); } @@ -842,6 +854,7 @@ mod tests { plan_type: None, resets_at: None, rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; assert_eq!( err.to_string(), @@ -859,6 +872,7 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Team)), resets_at: Some(resets_at), rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; let expected = format!( "You've hit your usage limit. To get more access now, send a request to your admin or try again at {expected_time}." @@ -873,6 +887,7 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Business)), resets_at: None, rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; assert_eq!( err.to_string(), @@ -886,6 +901,7 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Enterprise)), resets_at: None, rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; assert_eq!( err.to_string(), @@ -903,6 +919,7 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Pro)), resets_at: Some(resets_at), rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; let expected = format!( "You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at {expected_time}." @@ -921,6 +938,7 @@ mod tests { plan_type: None, resets_at: Some(resets_at), rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; let expected = format!("You've hit your usage limit. Try again at {expected_time}."); assert_eq!(err.to_string(), expected); @@ -972,9 +990,10 @@ mod tests { plan_type: Some(PlanType::Known(KnownPlan::Plus)), resets_at: Some(resets_at), rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; let expected = format!( - "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at {expected_time}." + "You've hit your usage limit. Upgrade to Pro (https://chatgpt.com/explore/pro), visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at {expected_time}." ); assert_eq!(err.to_string(), expected); }); @@ -991,6 +1010,7 @@ mod tests { plan_type: None, resets_at: Some(resets_at), rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; let expected = format!("You've hit your usage limit. Try again at {expected_time}."); assert_eq!(err.to_string(), expected); @@ -1007,9 +1027,31 @@ mod tests { plan_type: None, resets_at: Some(resets_at), rate_limits: Some(rate_limit_snapshot()), + promo_message: None, }; let expected = format!("You've hit your usage limit. Try again at {expected_time}."); assert_eq!(err.to_string(), expected); }); } + + #[test] + fn usage_limit_reached_with_promo_message() { + let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); + let resets_at = base + ChronoDuration::seconds(30); + with_now_override(base, move || { + let expected_time = format_retry_timestamp(&resets_at); + let err = UsageLimitReachedError { + plan_type: None, + resets_at: Some(resets_at), + rate_limits: Some(rate_limit_snapshot()), + promo_message: Some( + "To continue using Codex, start a free trial of today".to_string(), + ), + }; + let expected = format!( + "You've hit your usage limit. To continue using Codex, start a free trial of today, or try again at {expected_time}." + ); + assert_eq!(err.to_string(), expected); + }); + } } From 9a10121fd6e46866c9eade1d1b876a270b1b6d72 Mon Sep 17 00:00:00 2001 From: douglaz Date: Sat, 31 Jan 2026 16:34:53 -0300 Subject: [PATCH 037/139] fix(nix): update flake for newer Rust toolchain requirements (#10302) ## Summary - Add rust-overlay input to provide newer Rust versions (rama crates require rustc 1.91.0+) - Add devShells output with complete development environment - Add missing git dependency hashes to codex-rs/default.nix ## Changes **flake.nix:** - Added `rust-overlay` input to get newer Rust toolchains - Updated `packages` output to use `rust-bin.stable.latest.minimal` for builds - Added `devShells` output with: - Rust with `rust-src` and `rust-analyzer` extensions for IDE support - Required build dependencies: `pkg-config`, `openssl`, `cmake`, `libclang` - Environment variables: `PKG_CONFIG_PATH`, `LIBCLANG_PATH` **codex-rs/default.nix:** - Added missing `outputHashes` for git dependencies: - `nucleo-0.5.0`, `nucleo-matcher-0.3.1` - `runfiles-0.1.0` - `tokio-tungstenite-0.28.0`, `tungstenite-0.28.0` ## Test Plan - [x] `nix develop` enters shell successfully - [x] `nix develop -c rustc --version` shows 1.93.0 - [x] `nix develop -c cargo build` completes successfully --- codex-rs/default.nix | 5 +++++ flake.lock | 29 ++++++++++++++++++++---- flake.nix | 53 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/codex-rs/default.nix b/codex-rs/default.nix index 26971f1846..8ad019a1de 100644 --- a/codex-rs/default.nix +++ b/codex-rs/default.nix @@ -22,6 +22,11 @@ rustPlatform.buildRustPackage (_: { cargoLock.outputHashes = { "ratatui-0.29.0" = "sha256-HBvT5c8GsiCxMffNjJGLmHnvG77A6cqEL+1ARurBXho="; "crossterm-0.28.1" = "sha256-6qCtfSMuXACKFb9ATID39XyFDIEMFDmbx6SSmNe+728="; + "nucleo-0.5.0" = "sha256-Hm4SxtTSBrcWpXrtSqeO0TACbUxq3gizg1zD/6Yw/sI="; + "nucleo-matcher-0.3.1" = "sha256-Hm4SxtTSBrcWpXrtSqeO0TACbUxq3gizg1zD/6Yw/sI="; + "runfiles-0.1.0" = "sha256-uJpVLcQh8wWZA3GPv9D8Nt43EOirajfDJ7eq/FB+tek="; + "tokio-tungstenite-0.28.0" = "sha256-vJZ3S41gHtRt4UAODsjAoSCaTksgzCALiBmbWgyDCi8="; + "tungstenite-0.28.0" = "sha256-CyXZp58zGlUhEor7WItjQoS499IoSP55uWqr++ia+0A="; }; meta = with lib; { diff --git a/flake.lock b/flake.lock index 21550893f4..8348ebd272 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1758427187, - "narHash": "sha256-pHpxZ/IyCwoTQPtFIAG2QaxuSm8jWzrzBGjwQZIttJc=", + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "554be6495561ff07b6c724047bdd7e0716aa7b46", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", "type": "github" }, "original": { @@ -18,7 +18,28 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769828398, + "narHash": "sha256-zmnvRUm15QrlKH0V1BZoiT3U+Q+tr+P5Osi8qgtL9fY=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "a1d32c90c8a4ea43e9586b7e5894c179d5747425", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index b331c443bb..711172d737 100644 --- a/flake.nix +++ b/flake.nix @@ -1,9 +1,15 @@ { description = "Development Nix flake for OpenAI Codex CLI"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; - outputs = { nixpkgs, ... }: + outputs = { nixpkgs, rust-overlay, ... }: let systems = [ "x86_64-linux" @@ -16,13 +22,52 @@ { packages = forAllSystems (system: let - pkgs = nixpkgs.legacyPackages.${system}; - codex-rs = pkgs.callPackage ./codex-rs { }; + pkgs = import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + }; + codex-rs = pkgs.callPackage ./codex-rs { + rustPlatform = pkgs.makeRustPlatform { + cargo = pkgs.rust-bin.stable.latest.minimal; + rustc = pkgs.rust-bin.stable.latest.minimal; + }; + }; in { codex-rs = codex-rs; default = codex-rs; } ); + + devShells = forAllSystems (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + }; + rust = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" ]; + }; + in + { + default = pkgs.mkShell { + buildInputs = [ + rust + pkgs.pkg-config + pkgs.openssl + pkgs.cmake + pkgs.llvmPackages.clang + pkgs.llvmPackages.libclang.lib + ]; + PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + # Use clang for BoringSSL compilation (avoids GCC 15 warnings-as-errors) + shellHook = '' + export CC=clang + export CXX=clang++ + ''; + }; + } + ); }; } From 28f3a718095ba34a636152d4878310469a26bfdb Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sat, 31 Jan 2026 13:17:24 -0700 Subject: [PATCH 038/139] chore(features) remove Experimental tag from UTF8 (#10296) ## Summary This has been default on for some time, it should now be the default. ## Testing - [x] Existing tests pass --- codex-rs/core/src/features.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 0882b2ae76..0d81b18a0b 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -503,11 +503,7 @@ pub const FEATURES: &[FeatureSpec] = &[ id: Feature::PowershellUtf8, key: "powershell_utf8", #[cfg(windows)] - stage: Stage::Experimental { - name: "Powershell UTF-8 support", - menu_description: "Enable UTF-8 output in Powershell.", - announcement: "Codex now supports UTF-8 output in Powershell. If you are seeing problems, disable in /experimental.", - }, + stage: Stage::Stable, #[cfg(windows)] default_enabled: true, #[cfg(not(windows))] From 49342b156d55f0b7d7b7e63eae6702456966d998 Mon Sep 17 00:00:00 2001 From: Fouad Matin <169186268+fouad-openai@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:33:06 -0800 Subject: [PATCH 039/139] Fix npm README image link (#10303) ### Motivation - The image referenced in the package README was 404ing on the npm package page because it used a relative file path that doesn't resolve on npm, so the splash image needs a GitHub-hosted URL to render correctly. ### Description - Update `README.md` to replace the relative image path `./.github/codex-cli-splash.png` with the GitHub-hosted URL `https://github.com/openai/codex/blob/main/.github/codex-cli-splash.png`. ### Testing - No automated tests were run because this is a docs-only change and does not affect code or test behavior. ------ [Codex Task](https://chatgpt.com/codex/tasks/task_i_697e58dbce34832d87c7847779e8f4a5) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb4ace74f2..ae5073beae 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

npm i -g @openai/codex
or brew install --cask codex

Codex CLI is a coding agent from OpenAI that runs locally on your computer.

- Codex CLI splash + Codex CLI splash


If you want Codex in your code editor (VS Code, Cursor, Windsurf), install in your IDE. From ed9e02c9dc475ab548d9743855faf3075b2669d9 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sat, 31 Jan 2026 14:49:55 -0700 Subject: [PATCH 040/139] chore(app-server) add personality update test (#10306) ## Summary Add some additional validation to ensure app-server handles Personality changes ## Testing - [x] These are tests --- .../app-server/tests/suite/v2/turn_start.rs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 99eff9ecb2..99ad1ba839 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -484,6 +484,117 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> { Ok(()) } +#[tokio::test] +async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let sse1 = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_assistant_message("msg-1", "Done"), + responses::ev_completed("resp-1"), + ]); + let sse2 = responses::sse(vec![ + responses::ev_response_created("resp-2"), + responses::ev_assistant_message("msg-2", "Done"), + responses::ev_completed("resp-2"), + ]); + let response_mock = responses::mount_sse_sequence(&server, vec![sse1, sse2]).await; + + let codex_home = TempDir::new()?; + create_config_toml( + codex_home.path(), + &server.uri(), + "never", + &BTreeMap::from([(Feature::Personality, true)]), + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("exp-codex-personality".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id.clone(), + input: vec![V2UserInput::Text { + text: "Hello".to_string(), + text_elements: Vec::new(), + }], + personality: None, + ..Default::default() + }) + .await?; + let turn_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + let _turn: TurnStartResponse = to_response::(turn_resp)?; + + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let turn_req2 = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id.clone(), + input: vec![V2UserInput::Text { + text: "Hello again".to_string(), + text_elements: Vec::new(), + }], + personality: Some(Personality::Friendly), + ..Default::default() + }) + .await?; + let turn_resp2: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req2)), + ) + .await??; + let _turn2: TurnStartResponse = to_response::(turn_resp2)?; + + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let requests = response_mock.requests(); + assert_eq!(requests.len(), 2, "expected two requests"); + + let first_developer_texts = requests[0].message_input_texts("developer"); + assert!( + first_developer_texts + .iter() + .all(|text| !text.contains("")), + "expected no personality update message in first request, got {first_developer_texts:?}" + ); + + let second_developer_texts = requests[1].message_input_texts("developer"); + assert!( + second_developer_texts + .iter() + .any(|text| text.contains("")), + "expected personality update message in second request, got {second_developer_texts:?}" + ); + + Ok(()) +} + #[tokio::test] async fn turn_start_accepts_local_image_input() -> Result<()> { // Two Codex turns hit the mock model (session start + turn/start). From 2d6757430a05c4c619384b6f1d2f9c8847d4416e Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sat, 31 Jan 2026 13:55:52 -0800 Subject: [PATCH 041/139] plan mode prompt (#10308) # External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request. --- codex-rs/core/templates/collaboration_mode/plan.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/templates/collaboration_mode/plan.md b/codex-rs/core/templates/collaboration_mode/plan.md index f600bc9c7b..3b69f34fa1 100644 --- a/codex-rs/core/templates/collaboration_mode/plan.md +++ b/codex-rs/core/templates/collaboration_mode/plan.md @@ -42,6 +42,8 @@ When in doubt: if the action would reasonably be described as "doing the work" r Begin by grounding yourself in the actual environment. Eliminate unknowns in the prompt by discovering facts, not by asking the user. Resolve all questions that can be answered through exploration or inspection. Identify missing or ambiguous details only if they cannot be derived from the environment. Silent exploration between turns is allowed and encouraged. +Exception: simple, self-contained questions that does not need that. + Before asking the user any question, perform at least one targeted non-mutating exploration pass (for example: search relevant files, inspect likely entrypoints/configs, confirm current implementation shape), unless no local environment/repo is available. Do not ask questions that can be answered from the repo or system (for example, "where is this struct?" or "which UI component should we use?" when exploration can make it clear). Only ask once you have exhausted reasonable non-mutating exploration. @@ -59,12 +61,14 @@ Do not ask questions that can be answered from the repo or system (for example, Every assistant turn MUST be exactly one of: A) a `request_user_input` tool call (questions/options only), OR -B) the final output: a titled, plan-only document. +B) the final output: a titled, plan-only document, OR +C) Direct response to a simple, self-contained question (no planning, no implementation). Rules: * No questions in free text (only via `request_user_input`). * Never mix a `request_user_input` call with plan content. +* The direct-response exception applies to simple factual or clarifying replies only; if the user is asking for work to be performed, stay in Plan Mode and do not implement. * Internal tool/repo exploration is allowed privately before A or B. ## Ask a lot, but never ask trivia From 8a461765f3d84a6ce828f7be43d2d53879a563f4 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sat, 31 Jan 2026 17:11:32 -0700 Subject: [PATCH 042/139] chore(core) Default to friendly personality (#10305) ## Summary Update default personality to friendly ## Testing - [x] Unit tests pass --- .../tests/suite/v2/thread_resume.rs | 2 +- .../app-server/tests/suite/v2/turn_start.rs | 4 +- codex-rs/core/src/config/mod.rs | 7 ++- codex-rs/core/tests/suite/personality.rs | 46 ++++++++++--------- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 6ad0a14b30..efa51573cb 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -402,7 +402,7 @@ async fn thread_resume_accepts_personality_override() -> Result<()> { .send_thread_resume_request(ThreadResumeParams { thread_id: thread.id.clone(), model: Some("gpt-5.2-codex".to_string()), - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), ..Default::default() }) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 99ad1ba839..1b53a5cfef 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -451,7 +451,7 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> { text: "Hello".to_string(), text_elements: Vec::new(), }], - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), ..Default::default() }) .await?; @@ -556,7 +556,7 @@ async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { text: "Hello again".to_string(), text_elements: Vec::new(), }], - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), ..Default::default() }) .await?; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 6af91109dc..23d7ef96e5 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1499,7 +1499,12 @@ impl Config { let developer_instructions = developer_instructions.or(cfg.developer_instructions); let model_personality = model_personality .or(config_profile.model_personality) - .or(cfg.model_personality); + .or(cfg.model_personality) + .or_else(|| { + features + .enabled(Feature::Personality) + .then_some(Personality::Friendly) + }); let experimental_compact_prompt_path = config_profile .experimental_compact_prompt_file diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 79e0d36c68..e4fe3371a0 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -39,6 +39,7 @@ use wiremock::MockServer; const LOCAL_FRIENDLY_TEMPLATE: &str = "You optimize for team morale and being a supportive teammate as much as code quality."; +const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer."; fn sse_completed(id: &str) -> String { sse(vec![ev_response_created(id), ev_completed(id)]) @@ -223,7 +224,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> effort: None, summary: None, collaboration_mode: None, - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), }) .await?; @@ -264,8 +265,8 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> "expected personality update preamble, got {personality_text:?}" ); assert!( - personality_text.contains(LOCAL_FRIENDLY_TEMPLATE), - "expected personality update to include the local friendly template, got: {personality_text:?}" + personality_text.contains(LOCAL_PRAGMATIC_TEMPLATE), + "expected personality update to include the local pragmatic template, got: {personality_text:?}" ); Ok(()) @@ -335,7 +336,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()> effort: None, summary: None, collaboration_mode: None, - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), }) .await?; @@ -403,9 +404,7 @@ async fn ignores_remote_model_personality_if_remote_models_disabled() -> anyhow: upgrade: None, base_instructions: "base instructions".to_string(), model_messages: Some(ModelMessages { - instructions_template: Some( - "Base instructions\n{{ personality_message }}\n".to_string(), - ), + instructions_template: Some("Base instructions\n{{ personality }}\n".to_string()), instructions_variables: Some(ModelInstructionsVariables { personality_default: None, personality_friendly: Some(remote_personality_message.to_string()), @@ -485,7 +484,7 @@ async fn ignores_remote_model_personality_if_remote_models_disabled() -> anyhow: "expected instructions to include the local friendly personality template, got: {instructions_text:?}" ); assert!( - !instructions_text.contains("{{ personality_message }}"), + !instructions_text.contains("{{ personality }}"), "expected legacy personality placeholder to be replaced, got: {instructions_text:?}" ); @@ -493,7 +492,7 @@ async fn ignores_remote_model_personality_if_remote_models_disabled() -> anyhow: } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn remote_model_default_personality_instructions_with_feature() -> anyhow::Result<()> { +async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::builder() @@ -503,6 +502,7 @@ async fn remote_model_default_personality_instructions_with_feature() -> anyhow: let remote_slug = "codex-remote-default-personality"; let default_personality_message = "Default from remote template"; + let friendly_personality_message = "Friendly variant"; let remote_model = ModelInfo { slug: remote_slug.to_string(), display_name: "Remote default personality test".to_string(), @@ -522,7 +522,7 @@ async fn remote_model_default_personality_instructions_with_feature() -> anyhow: instructions_template: Some("Base instructions\n{{ personality }}\n".to_string()), instructions_variables: Some(ModelInstructionsVariables { personality_default: Some(default_personality_message.to_string()), - personality_friendly: Some("Friendly variant".to_string()), + personality_friendly: Some(friendly_personality_message.to_string()), personality_pragmatic: Some("Pragmatic variant".to_string()), }), }), @@ -554,6 +554,7 @@ async fn remote_model_default_personality_instructions_with_feature() -> anyhow: config.features.enable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.model = Some(remote_slug.to_string()); + config.model_personality = Some(Personality::Friendly); }); let test = builder.build(&server).await?; @@ -578,7 +579,7 @@ async fn remote_model_default_personality_instructions_with_feature() -> anyhow: effort: test.config.model_reasoning_effort, summary: ReasoningSummary::Auto, collaboration_mode: None, - personality: None, + personality: Some(Personality::Friendly), }) .await?; @@ -588,8 +589,12 @@ async fn remote_model_default_personality_instructions_with_feature() -> anyhow: let instructions_text = request.instructions_text(); assert!( - instructions_text.contains(default_personality_message), - "expected instructions to include the remote default personality template, got: {instructions_text:?}" + instructions_text.contains(friendly_personality_message), + "expected instructions to include the remote friendly personality template, got: {instructions_text:?}" + ); + assert!( + !instructions_text.contains(default_personality_message), + "expected instructions to skip the remote default personality template, got: {instructions_text:?}" ); Ok(()) @@ -606,7 +611,8 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - .await; let remote_slug = "codex-remote-personality"; - let remote_personality_message = "Friendly from remote template"; + let remote_friendly_message = "Friendly from remote template"; + let remote_pragmatic_message = "Pragmatic from remote template"; let remote_model = ModelInfo { slug: remote_slug.to_string(), display_name: "Remote personality test".to_string(), @@ -623,13 +629,11 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - upgrade: None, base_instructions: "base instructions".to_string(), model_messages: Some(ModelMessages { - instructions_template: Some( - "Base instructions\n{{ personality_message }}\n".to_string(), - ), + instructions_template: Some("Base instructions\n{{ personality }}\n".to_string()), instructions_variables: Some(ModelInstructionsVariables { personality_default: None, - personality_friendly: Some(remote_personality_message.to_string()), - personality_pragmatic: None, + personality_friendly: Some(remote_friendly_message.to_string()), + personality_pragmatic: Some(remote_pragmatic_message.to_string()), }), }), supports_reasoning_summaries: false, @@ -704,7 +708,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - effort: None, summary: None, collaboration_mode: None, - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), }) .await?; @@ -744,7 +748,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - "expected personality update preamble, got {personality_text:?}" ); assert!( - personality_text.contains(remote_personality_message), + personality_text.contains(remote_pragmatic_message), "expected personality update to include remote template, got: {personality_text:?}" ); From 0f9858394b9504245b1bbff32526dcea8bf487ec Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sat, 31 Jan 2026 17:25:14 -0700 Subject: [PATCH 043/139] feat(core,tui,app-server) personality migration (#10307) ## Summary Keep existing users on Pragmatic, to preserve behavior while new users default to Friendly ## Testing - [x] Tested locally - [x] add integration tests --- codex-rs/app-server/src/lib.rs | 17 ++ codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/personality_migration.rs | 265 ++++++++++++++++++ codex-rs/core/tests/suite/mod.rs | 1 + .../core/tests/suite/personality_migration.rs | 154 ++++++++++ codex-rs/tui/src/lib.rs | 7 + 6 files changed, 445 insertions(+) create mode 100644 codex-rs/core/src/personality_migration.rs create mode 100644 codex-rs/core/tests/suite/personality_migration.rs diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 52ea9325f1..f7d108f55f 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -215,6 +215,23 @@ pub async fn run_main( .await { Ok(config) => { + let effective_toml = config.config_layer_stack.effective_config(); + match effective_toml.try_into() { + Ok(config_toml) => { + if let Err(err) = codex_core::personality_migration::maybe_migrate_personality( + &config.codex_home, + &config_toml, + ) + .await + { + warn!(error = %err, "Failed to run personality migration"); + } + } + Err(err) => { + warn!(error = %err, "Failed to deserialize config for personality migration"); + } + } + let auth_manager = AuthManager::shared( config.codex_home.clone(), false, diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index ba47838e1f..75b53624f4 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -48,6 +48,7 @@ mod message_history; mod model_provider_info; pub mod parse_command; pub mod path_utils; +pub mod personality_migration; pub mod powershell; mod proposed_plan_parser; pub mod sandboxing; diff --git a/codex-rs/core/src/personality_migration.rs b/codex-rs/core/src/personality_migration.rs new file mode 100644 index 0000000000..cfe17c1711 --- /dev/null +++ b/codex-rs/core/src/personality_migration.rs @@ -0,0 +1,265 @@ +use crate::config::ConfigToml; +use crate::config::edit::ConfigEditsBuilder; +use crate::rollout::ARCHIVED_SESSIONS_SUBDIR; +use crate::rollout::SESSIONS_SUBDIR; +use crate::rollout::list::ThreadListConfig; +use crate::rollout::list::ThreadListLayout; +use crate::rollout::list::ThreadSortKey; +use crate::rollout::list::get_threads_in_root; +use crate::state_db; +use codex_protocol::config_types::Personality; +use codex_protocol::protocol::SessionSource; +use std::io; +use std::path::Path; +use tokio::fs::OpenOptions; +use tokio::io::AsyncWriteExt; + +pub const PERSONALITY_MIGRATION_FILENAME: &str = ".personality_migration"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PersonalityMigrationStatus { + SkippedMarker, + SkippedExplicitPersonality, + SkippedNoSessions, + Applied, +} + +pub async fn maybe_migrate_personality( + codex_home: &Path, + config_toml: &ConfigToml, +) -> io::Result { + let marker_path = codex_home.join(PERSONALITY_MIGRATION_FILENAME); + if tokio::fs::try_exists(&marker_path).await? { + return Ok(PersonalityMigrationStatus::SkippedMarker); + } + + let config_profile = config_toml + .get_config_profile(None) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + if config_toml.model_personality.is_some() || config_profile.model_personality.is_some() { + create_marker(&marker_path).await?; + return Ok(PersonalityMigrationStatus::SkippedExplicitPersonality); + } + + let model_provider_id = config_profile + .model_provider + .or_else(|| config_toml.model_provider.clone()) + .unwrap_or_else(|| "openai".to_string()); + + if !has_recorded_sessions(codex_home, model_provider_id.as_str()).await? { + create_marker(&marker_path).await?; + return Ok(PersonalityMigrationStatus::SkippedNoSessions); + } + + ConfigEditsBuilder::new(codex_home) + .set_model_personality(Some(Personality::Pragmatic)) + .apply() + .await + .map_err(|err| { + io::Error::other(format!("failed to persist personality migration: {err}")) + })?; + + create_marker(&marker_path).await?; + Ok(PersonalityMigrationStatus::Applied) +} + +async fn has_recorded_sessions(codex_home: &Path, default_provider: &str) -> io::Result { + let allowed_sources: &[SessionSource] = &[]; + + if let Some(state_db_ctx) = state_db::open_if_present(codex_home, default_provider).await + && let Some(ids) = state_db::list_thread_ids_db( + Some(state_db_ctx.as_ref()), + codex_home, + 1, + None, + ThreadSortKey::CreatedAt, + allowed_sources, + None, + false, + "personality_migration", + ) + .await + && !ids.is_empty() + { + return Ok(true); + } + + let sessions = get_threads_in_root( + codex_home.join(SESSIONS_SUBDIR), + 1, + None, + ThreadSortKey::CreatedAt, + ThreadListConfig { + allowed_sources, + model_providers: None, + default_provider, + layout: ThreadListLayout::NestedByDate, + }, + ) + .await?; + if !sessions.items.is_empty() { + return Ok(true); + } + + let archived_sessions = get_threads_in_root( + codex_home.join(ARCHIVED_SESSIONS_SUBDIR), + 1, + None, + ThreadSortKey::CreatedAt, + ThreadListConfig { + allowed_sources, + model_providers: None, + default_provider, + layout: ThreadListLayout::Flat, + }, + ) + .await?; + Ok(!archived_sessions.items.is_empty()) +} + +async fn create_marker(marker_path: &Path) -> io::Result<()> { + match OpenOptions::new() + .create_new(true) + .write(true) + .open(marker_path) + .await + { + Ok(mut file) => file.write_all(b"v1\n").await, + Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(()), + Err(err) => Err(err), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codex_protocol::ThreadId; + use codex_protocol::protocol::EventMsg; + use codex_protocol::protocol::RolloutItem; + use codex_protocol::protocol::RolloutLine; + use codex_protocol::protocol::SessionMeta; + use codex_protocol::protocol::SessionMetaLine; + use codex_protocol::protocol::SessionSource; + use codex_protocol::protocol::UserMessageEvent; + use pretty_assertions::assert_eq; + use tempfile::TempDir; + use tokio::io::AsyncWriteExt; + + const TEST_TIMESTAMP: &str = "2025-01-01T00-00-00"; + + async fn read_config_toml(codex_home: &Path) -> io::Result { + let contents = tokio::fs::read_to_string(codex_home.join("config.toml")).await?; + toml::from_str(&contents).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) + } + + async fn write_session_with_user_event(codex_home: &Path) -> io::Result<()> { + let thread_id = ThreadId::new(); + let dir = codex_home + .join(SESSIONS_SUBDIR) + .join("2025") + .join("01") + .join("01"); + tokio::fs::create_dir_all(&dir).await?; + let file_path = dir.join(format!("rollout-{TEST_TIMESTAMP}-{thread_id}.jsonl")); + let mut file = tokio::fs::File::create(&file_path).await?; + + let session_meta = SessionMetaLine { + meta: SessionMeta { + id: thread_id, + forked_from_id: None, + timestamp: TEST_TIMESTAMP.to_string(), + cwd: std::path::PathBuf::from("."), + originator: "test_originator".to_string(), + cli_version: "test_version".to_string(), + source: SessionSource::Cli, + model_provider: None, + base_instructions: None, + dynamic_tools: None, + }, + git: None, + }; + let meta_line = RolloutLine { + timestamp: TEST_TIMESTAMP.to_string(), + item: RolloutItem::SessionMeta(session_meta), + }; + let user_event = RolloutLine { + timestamp: TEST_TIMESTAMP.to_string(), + item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + message: "hello".to_string(), + images: None, + local_images: Vec::new(), + text_elements: Vec::new(), + })), + }; + + file.write_all(format!("{}\n", serde_json::to_string(&meta_line)?).as_bytes()) + .await?; + file.write_all(format!("{}\n", serde_json::to_string(&user_event)?).as_bytes()) + .await?; + Ok(()) + } + + #[tokio::test] + async fn applies_when_sessions_exist_and_no_personality() -> io::Result<()> { + let temp = TempDir::new()?; + write_session_with_user_event(temp.path()).await?; + + let config_toml = ConfigToml::default(); + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!(status, PersonalityMigrationStatus::Applied); + assert!(temp.path().join(PERSONALITY_MIGRATION_FILENAME).exists()); + + let persisted = read_config_toml(temp.path()).await?; + assert_eq!(persisted.model_personality, Some(Personality::Pragmatic)); + Ok(()) + } + + #[tokio::test] + async fn skips_when_marker_exists() -> io::Result<()> { + let temp = TempDir::new()?; + create_marker(&temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?; + + let config_toml = ConfigToml::default(); + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!(status, PersonalityMigrationStatus::SkippedMarker); + assert!(!temp.path().join("config.toml").exists()); + Ok(()) + } + + #[tokio::test] + async fn skips_when_personality_explicit() -> io::Result<()> { + let temp = TempDir::new()?; + ConfigEditsBuilder::new(temp.path()) + .set_model_personality(Some(Personality::Friendly)) + .apply() + .await + .map_err(|err| io::Error::other(format!("failed to write config: {err}")))?; + + let config_toml = read_config_toml(temp.path()).await?; + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!( + status, + PersonalityMigrationStatus::SkippedExplicitPersonality + ); + assert!(temp.path().join(PERSONALITY_MIGRATION_FILENAME).exists()); + + let persisted = read_config_toml(temp.path()).await?; + assert_eq!(persisted.model_personality, Some(Personality::Friendly)); + Ok(()) + } + + #[tokio::test] + async fn skips_when_no_sessions() -> io::Result<()> { + let temp = TempDir::new()?; + let config_toml = ConfigToml::default(); + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!(status, PersonalityMigrationStatus::SkippedNoSessions); + assert!(temp.path().join(PERSONALITY_MIGRATION_FILENAME).exists()); + assert!(!temp.path().join("config.toml").exists()); + Ok(()) + } +} diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 0c3f5bf1a2..f34ca09b40 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -49,6 +49,7 @@ mod otel; mod pending_input; mod permissions_messages; mod personality; +mod personality_migration; mod prompt_caching; mod quota_exceeded; mod read_file; diff --git a/codex-rs/core/tests/suite/personality_migration.rs b/codex-rs/core/tests/suite/personality_migration.rs new file mode 100644 index 0000000000..4431db2472 --- /dev/null +++ b/codex-rs/core/tests/suite/personality_migration.rs @@ -0,0 +1,154 @@ +use codex_core::ARCHIVED_SESSIONS_SUBDIR; +use codex_core::SESSIONS_SUBDIR; +use codex_core::config::ConfigToml; +use codex_core::personality_migration::PERSONALITY_MIGRATION_FILENAME; +use codex_core::personality_migration::PersonalityMigrationStatus; +use codex_core::personality_migration::maybe_migrate_personality; +use codex_protocol::ThreadId; +use codex_protocol::config_types::Personality; +use codex_protocol::protocol::EventMsg; +use codex_protocol::protocol::RolloutItem; +use codex_protocol::protocol::RolloutLine; +use codex_protocol::protocol::SessionMeta; +use codex_protocol::protocol::SessionMetaLine; +use codex_protocol::protocol::SessionSource; +use codex_protocol::protocol::UserMessageEvent; +use pretty_assertions::assert_eq; +use std::io; +use std::path::Path; +use tempfile::TempDir; +use tokio::io::AsyncWriteExt; + +const TEST_TIMESTAMP: &str = "2025-01-01T00-00-00"; + +async fn read_config_toml(codex_home: &Path) -> io::Result { + let contents = tokio::fs::read_to_string(codex_home.join("config.toml")).await?; + toml::from_str(&contents).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) +} + +async fn write_session_with_user_event(codex_home: &Path) -> io::Result<()> { + let thread_id = ThreadId::new(); + let dir = codex_home + .join(SESSIONS_SUBDIR) + .join("2025") + .join("01") + .join("01"); + write_rollout_with_user_event(&dir, thread_id).await +} + +async fn write_archived_session_with_user_event(codex_home: &Path) -> io::Result<()> { + let thread_id = ThreadId::new(); + let dir = codex_home.join(ARCHIVED_SESSIONS_SUBDIR); + write_rollout_with_user_event(&dir, thread_id).await +} + +async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::Result<()> { + tokio::fs::create_dir_all(&dir).await?; + let file_path = dir.join(format!("rollout-{TEST_TIMESTAMP}-{thread_id}.jsonl")); + let mut file = tokio::fs::File::create(&file_path).await?; + + let session_meta = SessionMetaLine { + meta: SessionMeta { + id: thread_id, + forked_from_id: None, + timestamp: TEST_TIMESTAMP.to_string(), + cwd: std::path::PathBuf::from("."), + originator: "test_originator".to_string(), + cli_version: "test_version".to_string(), + source: SessionSource::Cli, + model_provider: None, + base_instructions: None, + dynamic_tools: None, + }, + git: None, + }; + let meta_line = RolloutLine { + timestamp: TEST_TIMESTAMP.to_string(), + item: RolloutItem::SessionMeta(session_meta), + }; + let user_event = RolloutLine { + timestamp: TEST_TIMESTAMP.to_string(), + item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + message: "hello".to_string(), + images: None, + local_images: Vec::new(), + text_elements: Vec::new(), + })), + }; + + let meta_json = serde_json::to_string(&meta_line)?; + file.write_all(format!("{meta_json}\n").as_bytes()).await?; + let user_json = serde_json::to_string(&user_event)?; + file.write_all(format!("{user_json}\n").as_bytes()).await?; + Ok(()) +} + +#[tokio::test] +async fn migration_marker_exists_no_sessions_no_change() -> io::Result<()> { + let temp = TempDir::new()?; + let marker_path = temp.path().join(PERSONALITY_MIGRATION_FILENAME); + tokio::fs::write(&marker_path, "v1\n").await?; + + let status = maybe_migrate_personality(temp.path(), &ConfigToml::default()).await?; + + assert_eq!(status, PersonalityMigrationStatus::SkippedMarker); + assert_eq!( + tokio::fs::try_exists(temp.path().join("config.toml")).await?, + false + ); + Ok(()) +} + +#[tokio::test] +async fn no_marker_no_sessions_no_change() -> io::Result<()> { + let temp = TempDir::new()?; + + let status = maybe_migrate_personality(temp.path(), &ConfigToml::default()).await?; + + assert_eq!(status, PersonalityMigrationStatus::SkippedNoSessions); + assert_eq!( + tokio::fs::try_exists(temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?, + true + ); + assert_eq!( + tokio::fs::try_exists(temp.path().join("config.toml")).await?, + false + ); + Ok(()) +} + +#[tokio::test] +async fn no_marker_sessions_sets_personality() -> io::Result<()> { + let temp = TempDir::new()?; + write_session_with_user_event(temp.path()).await?; + + let status = maybe_migrate_personality(temp.path(), &ConfigToml::default()).await?; + + assert_eq!(status, PersonalityMigrationStatus::Applied); + assert_eq!( + tokio::fs::try_exists(temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?, + true + ); + + let persisted = read_config_toml(temp.path()).await?; + assert_eq!(persisted.model_personality, Some(Personality::Pragmatic)); + Ok(()) +} + +#[tokio::test] +async fn no_marker_archived_sessions_sets_personality() -> io::Result<()> { + let temp = TempDir::new()?; + write_archived_session_with_user_event(temp.path()).await?; + + let status = maybe_migrate_personality(temp.path(), &ConfigToml::default()).await?; + + assert_eq!(status, PersonalityMigrationStatus::Applied); + assert_eq!( + tokio::fs::try_exists(temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?, + true + ); + + let persisted = read_config_toml(temp.path()).await?; + assert_eq!(persisted.model_personality, Some(Personality::Pragmatic)); + Ok(()) +} diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 82a06a7032..dc29030434 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -209,6 +209,13 @@ pub async fn run_main( } }; + if let Err(err) = + codex_core::personality_migration::maybe_migrate_personality(&codex_home, &config_toml) + .await + { + tracing::warn!(error = %err, "failed to run personality migration"); + } + let cloud_auth_manager = AuthManager::shared( codex_home.to_path_buf(), false, From 30ed29a7b37e183f151ea04362bb22b496dc456e Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sat, 31 Jan 2026 16:58:17 -0800 Subject: [PATCH 044/139] enable plan mode (#10313) # External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request. --- codex-rs/core/src/features.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 0d81b18a0b..e4b5c22b05 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -558,8 +558,8 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::CollaborationModes, key: "collaboration_modes", - stage: Stage::UnderDevelopment, - default_enabled: false, + stage: Stage::Stable, + default_enabled: true, }, FeatureSpec { id: Feature::Personality, From b164ac6d1e9df800ea241e1a01d06474a43a815c Mon Sep 17 00:00:00 2001 From: alexsong-oai Date: Sat, 31 Jan 2026 18:06:26 -0800 Subject: [PATCH 045/139] feat: fire tracking events for skill invocation (#10120) --- codex-rs/core/src/analytics_client.rs | 331 ++++++++++++++++++++++++++ codex-rs/core/src/codex.rs | 24 +- codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/skills/injection.rs | 13 + codex-rs/core/src/state/service.rs | 2 + 5 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 codex-rs/core/src/analytics_client.rs diff --git a/codex-rs/core/src/analytics_client.rs b/codex-rs/core/src/analytics_client.rs new file mode 100644 index 0000000000..d625166b09 --- /dev/null +++ b/codex-rs/core/src/analytics_client.rs @@ -0,0 +1,331 @@ +use crate::AuthManager; +use crate::config::Config; +use crate::default_client::create_client; +use crate::git_info::collect_git_info; +use crate::git_info::get_git_repo_root; +use codex_protocol::protocol::SkillScope; +use serde::Serialize; +use sha1::Digest; +use sha1::Sha1; +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::mpsc; + +#[derive(Clone)] +pub(crate) struct TrackEventsContext { + pub(crate) model_slug: String, + pub(crate) thread_id: String, +} + +pub(crate) fn build_track_events_context( + model_slug: String, + thread_id: String, +) -> TrackEventsContext { + TrackEventsContext { + model_slug, + thread_id, + } +} + +pub(crate) struct SkillInvocation { + pub(crate) skill_name: String, + pub(crate) skill_scope: SkillScope, + pub(crate) skill_path: PathBuf, +} + +#[derive(Clone)] +pub(crate) struct AnalyticsEventsQueue { + sender: mpsc::Sender, +} + +pub(crate) struct AnalyticsEventsClient { + queue: AnalyticsEventsQueue, + config: Arc, +} + +impl AnalyticsEventsQueue { + pub(crate) fn new(auth_manager: Arc) -> Self { + let (sender, mut receiver) = mpsc::channel(ANALYTICS_EVENTS_QUEUE_SIZE); + tokio::spawn(async move { + while let Some(job) = receiver.recv().await { + send_track_skill_invocations(&auth_manager, job).await; + } + }); + Self { sender } + } + + fn try_send(&self, job: TrackEventsJob) { + if self.sender.try_send(job).is_err() { + //TODO: add a metric for this + tracing::warn!("dropping skill analytics events: queue is full"); + } + } +} + +impl AnalyticsEventsClient { + pub(crate) fn new(config: Arc, auth_manager: Arc) -> Self { + Self { + queue: AnalyticsEventsQueue::new(Arc::clone(&auth_manager)), + config, + } + } + + pub(crate) fn track_skill_invocations( + &self, + tracking: TrackEventsContext, + invocations: Vec, + ) { + track_skill_invocations( + &self.queue, + Arc::clone(&self.config), + Some(tracking), + invocations, + ); + } +} + +struct TrackEventsJob { + config: Arc, + tracking: TrackEventsContext, + invocations: Vec, +} + +const ANALYTICS_EVENTS_QUEUE_SIZE: usize = 256; +const ANALYTICS_EVENTS_TIMEOUT: Duration = Duration::from_secs(10); + +#[derive(Serialize)] +struct TrackEventsRequest { + events: Vec, +} + +#[derive(Serialize)] +struct TrackEvent { + event_type: &'static str, + skill_id: String, + skill_name: String, + event_params: TrackEventParams, +} + +#[derive(Serialize)] +struct TrackEventParams { + product_client_id: Option, + skill_scope: Option, + repo_url: Option, + thread_id: Option, + invoke_type: Option, + model_slug: Option, +} + +pub(crate) fn track_skill_invocations( + queue: &AnalyticsEventsQueue, + config: Arc, + tracking: Option, + invocations: Vec, +) { + if config.analytics_enabled == Some(false) { + return; + } + let Some(tracking) = tracking else { + return; + }; + if invocations.is_empty() { + return; + } + let job = TrackEventsJob { + config, + tracking, + invocations, + }; + queue.try_send(job); +} + +async fn send_track_skill_invocations(auth_manager: &AuthManager, job: TrackEventsJob) { + let TrackEventsJob { + config, + tracking, + invocations, + } = job; + let Some(auth) = auth_manager.auth().await else { + return; + }; + if !auth.is_chatgpt_auth() { + return; + } + let access_token = match auth.get_token() { + Ok(token) => token, + Err(_) => return, + }; + let Some(account_id) = auth.get_account_id() else { + return; + }; + + let mut events = Vec::with_capacity(invocations.len()); + for invocation in invocations { + let skill_scope = match invocation.skill_scope { + SkillScope::User => "user", + SkillScope::Repo => "repo", + SkillScope::System => "system", + SkillScope::Admin => "admin", + }; + let repo_root = get_git_repo_root(invocation.skill_path.as_path()); + let repo_url = if let Some(root) = repo_root.as_ref() { + collect_git_info(root) + .await + .and_then(|info| info.repository_url) + } else { + None + }; + let skill_id = skill_id_for_local_skill( + repo_url.as_deref(), + repo_root.as_deref(), + invocation.skill_path.as_path(), + invocation.skill_name.as_str(), + ); + events.push(TrackEvent { + event_type: "skill_invocation", + skill_id, + skill_name: invocation.skill_name.clone(), + event_params: TrackEventParams { + thread_id: Some(tracking.thread_id.clone()), + invoke_type: Some("explicit".to_string()), + model_slug: Some(tracking.model_slug.clone()), + product_client_id: Some(crate::default_client::originator().value), + repo_url, + skill_scope: Some(skill_scope.to_string()), + }, + }); + } + + let base_url = config.chatgpt_base_url.trim_end_matches('/'); + let url = format!("{base_url}/codex/analytics-events/events"); + let payload = TrackEventsRequest { events }; + + let response = create_client() + .post(&url) + .timeout(ANALYTICS_EVENTS_TIMEOUT) + .bearer_auth(&access_token) + .header("chatgpt-account-id", &account_id) + .header("Content-Type", "application/json") + .json(&payload) + .send() + .await; + + match response { + Ok(response) if response.status().is_success() => {} + Ok(response) => { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + tracing::warn!("events failed with status {status}: {body}"); + } + Err(err) => { + tracing::warn!("failed to send events request: {err}"); + } + } +} + +fn skill_id_for_local_skill( + repo_url: Option<&str>, + repo_root: Option<&Path>, + skill_path: &Path, + skill_name: &str, +) -> String { + let path = normalize_path_for_skill_id(repo_url, repo_root, skill_path); + let prefix = if let Some(url) = repo_url { + format!("repo_{url}") + } else { + "personal".to_string() + }; + let raw_id = format!("{prefix}_{path}_{skill_name}"); + let mut hasher = Sha1::new(); + hasher.update(raw_id.as_bytes()); + format!("{:x}", hasher.finalize()) +} + +/// Returns a normalized path for skill ID construction. +/// +/// - Repo-scoped skills use a path relative to the repo root. +/// - User/admin/system skills use an absolute path. +fn normalize_path_for_skill_id( + repo_url: Option<&str>, + repo_root: Option<&Path>, + skill_path: &Path, +) -> String { + let resolved_path = + std::fs::canonicalize(skill_path).unwrap_or_else(|_| skill_path.to_path_buf()); + match (repo_url, repo_root) { + (Some(_), Some(root)) => { + let resolved_root = std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf()); + resolved_path + .strip_prefix(&resolved_root) + .unwrap_or(resolved_path.as_path()) + .to_string_lossy() + .replace('\\', "/") + } + _ => resolved_path.to_string_lossy().replace('\\', "/"), + } +} + +#[cfg(test)] +mod tests { + use super::normalize_path_for_skill_id; + use pretty_assertions::assert_eq; + use std::path::PathBuf; + + fn expected_absolute_path(path: &PathBuf) -> String { + std::fs::canonicalize(path) + .unwrap_or_else(|_| path.to_path_buf()) + .to_string_lossy() + .replace('\\', "/") + } + + #[test] + fn normalize_path_for_skill_id_repo_scoped_uses_relative_path() { + let repo_root = PathBuf::from("/repo/root"); + let skill_path = PathBuf::from("/repo/root/.codex/skills/doc/SKILL.md"); + + let path = normalize_path_for_skill_id( + Some("https://example.com/repo.git"), + Some(repo_root.as_path()), + skill_path.as_path(), + ); + + assert_eq!(path, ".codex/skills/doc/SKILL.md"); + } + + #[test] + fn normalize_path_for_skill_id_user_scoped_uses_absolute_path() { + let skill_path = PathBuf::from("/Users/abc/.codex/skills/doc/SKILL.md"); + + let path = normalize_path_for_skill_id(None, None, skill_path.as_path()); + let expected = expected_absolute_path(&skill_path); + + assert_eq!(path, expected); + } + + #[test] + fn normalize_path_for_skill_id_admin_scoped_uses_absolute_path() { + let skill_path = PathBuf::from("/etc/codex/skills/doc/SKILL.md"); + + let path = normalize_path_for_skill_id(None, None, skill_path.as_path()); + let expected = expected_absolute_path(&skill_path); + + assert_eq!(path, expected); + } + + #[test] + fn normalize_path_for_skill_id_repo_root_not_in_skill_path_uses_absolute_path() { + let repo_root = PathBuf::from("/repo/root"); + let skill_path = PathBuf::from("/other/path/.codex/skills/doc/SKILL.md"); + + let path = normalize_path_for_skill_id( + Some("https://example.com/repo.git"), + Some(repo_root.as_path()), + skill_path.as_path(), + ); + let expected = expected_absolute_path(&skill_path); + + assert_eq!(path, expected); + } +} diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 64d79d392f..f987357874 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -14,6 +14,8 @@ use crate::agent::AgentControl; use crate::agent::AgentStatus; use crate::agent::MAX_THREAD_SPAWN_DEPTH; use crate::agent::agent_status_from_event; +use crate::analytics_client::AnalyticsEventsClient; +use crate::analytics_client::build_track_events_context; use crate::compact; use crate::compact::run_inline_auto_compact_task; use crate::compact::should_use_remote_compact_task; @@ -904,6 +906,10 @@ impl Session { mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), unified_exec_manager: UnifiedExecProcessManager::default(), + analytics_events_client: AnalyticsEventsClient::new( + Arc::clone(&config), + Arc::clone(&auth_manager), + ), notifier: UserNotifier::new(config.notify.clone()), rollout: Mutex::new(rollout_recorder), user_shell: Arc::new(default_shell), @@ -3385,10 +3391,18 @@ pub(crate) async fn run_turn( .await; let otel_manager = turn_context.client.get_otel_manager(); + let thread_id = sess.conversation_id.to_string(); + let tracking = build_track_events_context(turn_context.client.get_model(), thread_id); let SkillInjections { items: skill_items, warnings: skill_warnings, - } = build_skill_injections(&mentioned_skills, Some(&otel_manager)).await; + } = build_skill_injections( + &mentioned_skills, + Some(&otel_manager), + &sess.services.analytics_events_client, + tracking.clone(), + ) + .await; for message in skill_warnings { sess.send_event(&turn_context, EventMsg::Warning(WarningEvent { message })) @@ -5347,6 +5361,10 @@ mod tests { mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), unified_exec_manager: UnifiedExecProcessManager::default(), + analytics_events_client: AnalyticsEventsClient::new( + Arc::clone(&config), + Arc::clone(&auth_manager), + ), notifier: UserNotifier::new(None), rollout: Mutex::new(None), user_shell: Arc::new(default_user_shell()), @@ -5463,6 +5481,10 @@ mod tests { mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), unified_exec_manager: UnifiedExecProcessManager::default(), + analytics_events_client: AnalyticsEventsClient::new( + Arc::clone(&config), + Arc::clone(&auth_manager), + ), notifier: UserNotifier::new(None), rollout: Mutex::new(None), user_shell: Arc::new(default_user_shell()), diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 75b53624f4..94ba2f26b0 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -5,6 +5,7 @@ // the TUI or the tracing stack). #![deny(clippy::print_stdout, clippy::print_stderr)] +mod analytics_client; pub mod api_bridge; mod apply_patch; pub mod auth; diff --git a/codex-rs/core/src/skills/injection.rs b/codex-rs/core/src/skills/injection.rs index 57de457e90..77b3ceea10 100644 --- a/codex-rs/core/src/skills/injection.rs +++ b/codex-rs/core/src/skills/injection.rs @@ -2,6 +2,9 @@ use std::collections::HashMap; use std::collections::HashSet; use std::path::PathBuf; +use crate::analytics_client::AnalyticsEventsClient; +use crate::analytics_client::SkillInvocation; +use crate::analytics_client::TrackEventsContext; use crate::instructions::SkillInstructions; use crate::skills::SkillMetadata; use codex_otel::OtelManager; @@ -18,6 +21,8 @@ pub(crate) struct SkillInjections { pub(crate) async fn build_skill_injections( mentioned_skills: &[SkillMetadata], otel: Option<&OtelManager>, + analytics_client: &AnalyticsEventsClient, + tracking: TrackEventsContext, ) -> SkillInjections { if mentioned_skills.is_empty() { return SkillInjections::default(); @@ -27,11 +32,17 @@ pub(crate) async fn build_skill_injections( items: Vec::with_capacity(mentioned_skills.len()), warnings: Vec::new(), }; + let mut invocations = Vec::new(); for skill in mentioned_skills { match fs::read_to_string(&skill.path).await { Ok(contents) => { emit_skill_injected_metric(otel, skill, "ok"); + invocations.push(SkillInvocation { + skill_name: skill.name.clone(), + skill_scope: skill.scope, + skill_path: skill.path.clone(), + }); result.items.push(ResponseItem::from(SkillInstructions { name: skill.name.clone(), path: skill.path.to_string_lossy().into_owned(), @@ -50,6 +61,8 @@ pub(crate) async fn build_skill_injections( } } + analytics_client.track_skill_invocations(tracking, invocations); + result } diff --git a/codex-rs/core/src/state/service.rs b/codex-rs/core/src/state/service.rs index e036b29b7d..d7788f71cb 100644 --- a/codex-rs/core/src/state/service.rs +++ b/codex-rs/core/src/state/service.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use crate::AuthManager; use crate::RolloutRecorder; use crate::agent::AgentControl; +use crate::analytics_client::AnalyticsEventsClient; use crate::exec_policy::ExecPolicyManager; use crate::mcp_connection_manager::McpConnectionManager; use crate::models_manager::manager::ModelsManager; @@ -21,6 +22,7 @@ pub(crate) struct SessionServices { pub(crate) mcp_connection_manager: Arc>, pub(crate) mcp_startup_cancellation_token: Mutex, pub(crate) unified_exec_manager: UnifiedExecProcessManager, + pub(crate) analytics_events_client: AnalyticsEventsClient, pub(crate) notifier: UserNotifier, pub(crate) rollout: Mutex>, pub(crate) user_shell: Arc, From 39a6a84097ec4bdf180f42e5690f71159faa5670 Mon Sep 17 00:00:00 2001 From: Gav Verma Date: Sat, 31 Jan 2026 18:45:05 -0800 Subject: [PATCH 046/139] feat: Support loading skills from .agents/skills (#10317) This PR adds support for loading [skills](https://developers.openai.com/codex/skills) from `.agents/skills/`. - Issue: https://github.com/agentskills/agentskills/issues/15 - Motivation: When skills live on the filesystem, sharing them across agents is awkward and often ends up requiring symlinks/duplication. A single location under `.agents/` makes it easier to share skills. - Loading from `.codex/skills/` will remain but will be deprecated soon. The change only applies to the [REPO scope](https://developers.openai.com/codex/skills#where-to-save-skills). - Documentation will be updated before this change is live. Testing with skills in two locations of this repo: image When starting Codex with CWD in `$repo_root` (should only pick up at root): image When starting Codex with CWD in `$repo_root/codex-rs` (should pick up at cwd and crawl up to root): image --- codex-rs/core/src/config_loader/mod.rs | 6 +- codex-rs/core/src/skills/loader.rs | 130 ++++++++++++++++++++++++- codex-rs/core/src/skills/manager.rs | 7 +- 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index d48bda18f6..d8813e23d7 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -425,7 +425,9 @@ async fn load_requirements_from_legacy_scheme( /// empty array, which indicates that root detection should be disabled). /// - Returns an error if `project_root_markers` is specified but is not an /// array of strings. -fn project_root_markers_from_config(config: &TomlValue) -> io::Result>> { +pub(crate) fn project_root_markers_from_config( + config: &TomlValue, +) -> io::Result>> { let Some(table) = config.as_table() else { return Ok(None); }; @@ -454,7 +456,7 @@ fn project_root_markers_from_config(config: &TomlValue) -> io::Result Vec { +pub(crate) fn default_project_root_markers() -> Vec { DEFAULT_PROJECT_ROOT_MARKERS .iter() .map(ToString::to_string) diff --git a/codex-rs/core/src/skills/loader.rs b/codex-rs/core/src/skills/loader.rs index 32f1b8706e..a3c7731a6e 100644 --- a/codex-rs/core/src/skills/loader.rs +++ b/codex-rs/core/src/skills/loader.rs @@ -1,6 +1,9 @@ use crate::config::Config; use crate::config_loader::ConfigLayerStack; use crate::config_loader::ConfigLayerStackOrdering; +use crate::config_loader::default_project_root_markers; +use crate::config_loader::merge_toml_values; +use crate::config_loader::project_root_markers_from_config; use crate::skills::model::SkillDependencies; use crate::skills::model::SkillError; use crate::skills::model::SkillInterface; @@ -20,6 +23,7 @@ use std::fs; use std::path::Component; use std::path::Path; use std::path::PathBuf; +use toml::Value as TomlValue; use tracing::error; #[derive(Debug, Deserialize)] @@ -72,6 +76,7 @@ struct DependencyTool { } const SKILLS_FILENAME: &str = "SKILL.md"; +const AGENTS_DIR_NAME: &str = ".agents"; const SKILLS_METADATA_DIR: &str = "agents"; const SKILLS_METADATA_FILENAME: &str = "openai.yaml"; const SKILLS_DIR_NAME: &str = "skills"; @@ -209,15 +214,104 @@ fn skill_roots_from_layer_stack_inner(config_layer_stack: &ConfigLayerStack) -> } fn skill_roots(config: &Config) -> Vec { - skill_roots_from_layer_stack_inner(&config.config_layer_stack) + skill_roots_from_layer_stack_with_agents(&config.config_layer_stack, &config.cwd) } +#[cfg(test)] pub(crate) fn skill_roots_from_layer_stack( config_layer_stack: &ConfigLayerStack, ) -> Vec { skill_roots_from_layer_stack_inner(config_layer_stack) } +pub(crate) fn skill_roots_from_layer_stack_with_agents( + config_layer_stack: &ConfigLayerStack, + cwd: &Path, +) -> Vec { + let mut roots = skill_roots_from_layer_stack_inner(config_layer_stack); + roots.extend(repo_agents_skill_roots(config_layer_stack, cwd)); + dedupe_skill_roots_by_path(&mut roots); + roots +} + +fn dedupe_skill_roots_by_path(roots: &mut Vec) { + let mut seen: HashSet = HashSet::new(); + roots.retain(|root| seen.insert(root.path.clone())); +} + +fn repo_agents_skill_roots(config_layer_stack: &ConfigLayerStack, cwd: &Path) -> Vec { + let project_root_markers = project_root_markers_from_stack(config_layer_stack); + let project_root = find_project_root(cwd, &project_root_markers); + let dirs = dirs_between_project_root_and_cwd(cwd, &project_root); + let mut roots = Vec::new(); + for dir in dirs { + let agents_skills = dir.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME); + if agents_skills.is_dir() { + roots.push(SkillRoot { + path: agents_skills, + scope: SkillScope::Repo, + }); + } + } + roots +} + +fn project_root_markers_from_stack(config_layer_stack: &ConfigLayerStack) -> Vec { + let mut merged = TomlValue::Table(toml::map::Map::new()); + for layer in + config_layer_stack.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, false) + { + if matches!(layer.name, ConfigLayerSource::Project { .. }) { + continue; + } + merge_toml_values(&mut merged, &layer.config); + } + + match project_root_markers_from_config(&merged) { + Ok(Some(markers)) => markers, + Ok(None) => default_project_root_markers(), + Err(err) => { + tracing::warn!("invalid project_root_markers: {err}"); + default_project_root_markers() + } + } +} + +fn find_project_root(cwd: &Path, project_root_markers: &[String]) -> PathBuf { + if project_root_markers.is_empty() { + return cwd.to_path_buf(); + } + + for ancestor in cwd.ancestors() { + for marker in project_root_markers { + let marker_path = ancestor.join(marker); + if marker_path.exists() { + return ancestor.to_path_buf(); + } + } + } + + cwd.to_path_buf() +} + +fn dirs_between_project_root_and_cwd(cwd: &Path, project_root: &Path) -> Vec { + let mut dirs = cwd + .ancestors() + .scan(false, |done, a| { + if *done { + None + } else { + if a == project_root { + *done = true; + } + Some(a.to_path_buf()) + } + }) + .collect::>(); + dirs.reverse(); + dirs +} + fn discover_skills_under_root(root: &Path, scope: SkillScope, outcome: &mut SkillLoadOutcome) { let Ok(root) = canonicalize_path(root) else { return; @@ -1615,6 +1709,40 @@ interface: ); } + #[tokio::test] + async fn loads_skills_from_agents_dir_without_codex_dir() { + let codex_home = tempfile::tempdir().expect("tempdir"); + let repo_dir = tempfile::tempdir().expect("tempdir"); + mark_as_git_repo(repo_dir.path()); + + let skill_path = write_skill_at( + &repo_dir.path().join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME), + "agents", + "agents-skill", + "from agents", + ); + let cfg = make_config_for_cwd(&codex_home, repo_dir.path().to_path_buf()).await; + + let outcome = load_skills(&cfg); + assert!( + outcome.errors.is_empty(), + "unexpected errors: {:?}", + outcome.errors + ); + assert_eq!( + outcome.skills, + vec![SkillMetadata { + name: "agents-skill".to_string(), + description: "from agents".to_string(), + short_description: None, + interface: None, + dependencies: None, + path: normalized(&skill_path), + scope: SkillScope::Repo, + }] + ); + } + #[tokio::test] async fn loads_skills_from_all_codex_dirs_under_project_root() { let codex_home = tempfile::tempdir().expect("tempdir"); diff --git a/codex-rs/core/src/skills/manager.rs b/codex-rs/core/src/skills/manager.rs index 3b100991f7..85e0bf20eb 100644 --- a/codex-rs/core/src/skills/manager.rs +++ b/codex-rs/core/src/skills/manager.rs @@ -15,7 +15,7 @@ use crate::config_loader::LoaderOverrides; use crate::config_loader::load_config_layers_state; use crate::skills::SkillLoadOutcome; use crate::skills::loader::load_skills_from_roots; -use crate::skills::loader::skill_roots_from_layer_stack; +use crate::skills::loader::skill_roots_from_layer_stack_with_agents; use crate::skills::system::install_system_skills; pub struct SkillsManager { @@ -47,7 +47,8 @@ impl SkillsManager { return outcome; } - let roots = skill_roots_from_layer_stack(&config.config_layer_stack); + let roots = + skill_roots_from_layer_stack_with_agents(&config.config_layer_stack, &config.cwd); let mut outcome = load_skills_from_roots(roots); outcome.disabled_paths = disabled_paths_from_stack(&config.config_layer_stack); match self.cache_by_cwd.write() { @@ -105,7 +106,7 @@ impl SkillsManager { } }; - let roots = skill_roots_from_layer_stack(&config_layer_stack); + let roots = skill_roots_from_layer_stack_with_agents(&config_layer_stack, cwd); let mut outcome = load_skills_from_roots(roots); outcome.disabled_paths = disabled_paths_from_stack(&config_layer_stack); match self.cache_by_cwd.write() { From aab3705c7efbf12218f13cc9153bf7ecaee6f7ed Mon Sep 17 00:00:00 2001 From: xl-openai Date: Sat, 31 Jan 2026 22:08:25 -0500 Subject: [PATCH 047/139] Make skills prompt explicit about relative-path lookup (#10282) Fix cases where the model tries to locate skill scripts from the cwd and fails. --- codex-rs/core/src/project_doc.rs | 4 ++-- codex-rs/core/src/skills/render.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/src/project_doc.rs b/codex-rs/core/src/project_doc.rs index c763755af5..107477caa8 100644 --- a/codex-rs/core/src/project_doc.rs +++ b/codex-rs/core/src/project_doc.rs @@ -516,7 +516,7 @@ mod tests { ) .unwrap_or_else(|_| cfg.codex_home.join("skills/pdf-processing/SKILL.md")); let expected_path_str = expected_path.to_string_lossy().replace('\\', "/"); - let usage_rules = "- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 4) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."; + let usage_rules = "- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) When `SKILL.md` references relative paths (e.g., `scripts/foo.py`), resolve them relative to the skill directory listed above first, and only consider other paths if needed.\n 3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 5) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."; let expected = format!( "base doc\n\n## Skills\nA skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.\n### Available skills\n- pdf-processing: extract from pdfs (file: {expected_path_str})\n### How to use skills\n{usage_rules}" ); @@ -540,7 +540,7 @@ mod tests { dunce::canonicalize(cfg.codex_home.join("skills/linting/SKILL.md").as_path()) .unwrap_or_else(|_| cfg.codex_home.join("skills/linting/SKILL.md")); let expected_path_str = expected_path.to_string_lossy().replace('\\', "/"); - let usage_rules = "- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 4) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."; + let usage_rules = "- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) When `SKILL.md` references relative paths (e.g., `scripts/foo.py`), resolve them relative to the skill directory listed above first, and only consider other paths if needed.\n 3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 5) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."; let expected = format!( "## Skills\nA skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.\n### Available skills\n- linting: run clippy (file: {expected_path_str})\n### How to use skills\n{usage_rules}" ); diff --git a/codex-rs/core/src/skills/render.rs b/codex-rs/core/src/skills/render.rs index f998a51042..9627778dce 100644 --- a/codex-rs/core/src/skills/render.rs +++ b/codex-rs/core/src/skills/render.rs @@ -24,9 +24,10 @@ pub fn render_skills_section(skills: &[SkillMetadata]) -> Option { - Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback. - How to use a skill (progressive disclosure): 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow. - 2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything. - 3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks. - 4) If `assets/` or templates exist, reuse them instead of recreating from scratch. + 2) When `SKILL.md` references relative paths (e.g., `scripts/foo.py`), resolve them relative to the skill directory listed above first, and only consider other paths if needed. + 3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything. + 4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks. + 5) If `assets/` or templates exist, reuse them instead of recreating from scratch. - Coordination and sequencing: - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them. - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why. From 101d359cd7e99fc39190564ec097014e68e8a723 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Sat, 31 Jan 2026 19:16:44 -0800 Subject: [PATCH 048/139] Add websocket telemetry metrics and labels (#10316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary - expose websocket telemetry hooks through the responses client so request durations and event processing can be reported - record websocket request/event metrics and emit runtime telemetry events that the history UI now surfaces - improve tests to cover websocket telemetry reporting and guard runtime summary updates Screenshot 2026-01-31 at 5 28 12 PM --- codex-rs/Cargo.lock | 3 + .../src/endpoint/responses_websocket.rs | 34 ++++- codex-rs/codex-api/src/lib.rs | 1 + codex-rs/codex-api/src/sse/responses.rs | 2 +- codex-rs/codex-api/src/telemetry.rs | 14 ++ codex-rs/core/Cargo.toml | 5 + codex-rs/core/src/client.rs | 33 ++++- .../core/tests/suite/client_websockets.rs | 50 ++++++- codex-rs/otel/Cargo.toml | 1 + codex-rs/otel/src/metrics/names.rs | 4 + codex-rs/otel/src/metrics/runtime_metrics.rs | 22 ++- codex-rs/otel/src/traces/otel_manager.rs | 134 ++++++++++++++++++ codex-rs/otel/tests/suite/runtime_summary.rs | 17 +++ codex-rs/tui/src/history_cell.rs | 26 +++- 14 files changed, 335 insertions(+), 11 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 6047ca4725..cbe8aa6937 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1427,6 +1427,7 @@ dependencies = [ "multimap", "once_cell", "openssl-sys", + "opentelemetry_sdk", "os_info", "predicates", "pretty_assertions", @@ -1451,6 +1452,7 @@ dependencies = [ "thiserror 2.0.17", "time", "tokio", + "tokio-tungstenite", "tokio-util", "toml 0.9.5", "toml_edit 0.24.0+spec-1.1.0", @@ -1778,6 +1780,7 @@ dependencies = [ "strum_macros 0.27.2", "thiserror 2.0.17", "tokio", + "tokio-tungstenite", "tracing", "tracing-opentelemetry", "tracing-subscriber", diff --git a/codex-rs/codex-api/src/endpoint/responses_websocket.rs b/codex-rs/codex-api/src/endpoint/responses_websocket.rs index b088374b89..2a7d8726f1 100644 --- a/codex-rs/codex-api/src/endpoint/responses_websocket.rs +++ b/codex-rs/codex-api/src/endpoint/responses_websocket.rs @@ -6,6 +6,7 @@ use crate::error::ApiError; use crate::provider::Provider; use crate::sse::responses::ResponsesStreamEvent; use crate::sse::responses::process_responses_event; +use crate::telemetry::WebsocketTelemetry; use codex_client::TransportError; use futures::SinkExt; use futures::StreamExt; @@ -18,6 +19,7 @@ use std::time::Duration; use tokio::net::TcpStream; use tokio::sync::Mutex; use tokio::sync::mpsc; +use tokio::time::Instant; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::WebSocketStream; use tokio_tungstenite::tungstenite::Error as WsError; @@ -38,14 +40,21 @@ pub struct ResponsesWebsocketConnection { // TODO (pakrym): is this the right place for timeout? idle_timeout: Duration, server_reasoning_included: bool, + telemetry: Option>, } impl ResponsesWebsocketConnection { - fn new(stream: WsStream, idle_timeout: Duration, server_reasoning_included: bool) -> Self { + fn new( + stream: WsStream, + idle_timeout: Duration, + server_reasoning_included: bool, + telemetry: Option>, + ) -> Self { Self { stream: Arc::new(Mutex::new(Some(stream))), idle_timeout, server_reasoning_included, + telemetry, } } @@ -62,6 +71,7 @@ impl ResponsesWebsocketConnection { let stream = Arc::clone(&self.stream); let idle_timeout = self.idle_timeout; let server_reasoning_included = self.server_reasoning_included; + let telemetry = self.telemetry.clone(); let request_body = serde_json::to_value(&request).map_err(|err| { ApiError::Stream(format!("failed to encode websocket request: {err}")) })?; @@ -87,6 +97,7 @@ impl ResponsesWebsocketConnection { tx_event.clone(), request_body, idle_timeout, + telemetry, ) .await { @@ -114,6 +125,7 @@ impl ResponsesWebsocketClient { &self, extra_headers: HeaderMap, turn_state: Option>>, + telemetry: Option>, ) -> Result { let ws_url = self .provider @@ -130,6 +142,7 @@ impl ResponsesWebsocketClient { stream, self.provider.stream_idle_timeout, server_reasoning_included, + telemetry, )) } } @@ -218,6 +231,7 @@ async fn run_websocket_response_stream( tx_event: mpsc::Sender>, request_body: Value, idle_timeout: Duration, + telemetry: Option>, ) -> Result<(), ApiError> { let request_text = match serde_json::to_string(&request_body) { Ok(text) => text, @@ -228,16 +242,26 @@ async fn run_websocket_response_stream( } }; - if let Err(err) = ws_stream.send(Message::Text(request_text.into())).await { - return Err(ApiError::Stream(format!( - "failed to send websocket request: {err}" - ))); + let request_start = Instant::now(); + let result = ws_stream + .send(Message::Text(request_text.into())) + .await + .map_err(|err| ApiError::Stream(format!("failed to send websocket request: {err}"))); + + if let Some(t) = telemetry.as_ref() { + t.on_ws_request(request_start.elapsed(), result.as_ref().err()); } + result?; + loop { + let poll_start = Instant::now(); let response = tokio::time::timeout(idle_timeout, ws_stream.next()) .await .map_err(|_| ApiError::Stream("idle timeout waiting for websocket".into())); + if let Some(t) = telemetry.as_ref() { + t.on_ws_event(&response, poll_start.elapsed()); + } let message = match response { Ok(Some(Ok(msg))) => msg, Ok(Some(Err(err))) => { diff --git a/codex-rs/codex-api/src/lib.rs b/codex-rs/codex-api/src/lib.rs index 89e5c4dc5f..2e340970ca 100644 --- a/codex-rs/codex-api/src/lib.rs +++ b/codex-rs/codex-api/src/lib.rs @@ -40,3 +40,4 @@ pub use crate::requests::ResponsesRequest; pub use crate::requests::ResponsesRequestBuilder; pub use crate::sse::stream_from_fixture; pub use crate::telemetry::SseTelemetry; +pub use crate::telemetry::WebsocketTelemetry; diff --git a/codex-rs/codex-api/src/sse/responses.rs b/codex-rs/codex-api/src/sse/responses.rs index ad94cf5ea1..b363671a11 100644 --- a/codex-rs/codex-api/src/sse/responses.rs +++ b/codex-rs/codex-api/src/sse/responses.rs @@ -157,7 +157,7 @@ struct ResponseCompletedOutputTokensDetails { #[derive(Deserialize, Debug)] pub struct ResponsesStreamEvent { #[serde(rename = "type")] - kind: String, + pub(crate) kind: String, response: Option, item: Option, delta: Option, diff --git a/codex-rs/codex-api/src/telemetry.rs b/codex-rs/codex-api/src/telemetry.rs index d6a38b2af3..7b04fd2113 100644 --- a/codex-rs/codex-api/src/telemetry.rs +++ b/codex-rs/codex-api/src/telemetry.rs @@ -1,3 +1,4 @@ +use crate::error::ApiError; use codex_client::Request; use codex_client::RequestTelemetry; use codex_client::Response; @@ -10,6 +11,8 @@ use std::future::Future; use std::sync::Arc; use std::time::Duration; use tokio::time::Instant; +use tokio_tungstenite::tungstenite::Error; +use tokio_tungstenite::tungstenite::Message; /// Generic telemetry. pub trait SseTelemetry: Send + Sync { @@ -28,6 +31,17 @@ pub trait SseTelemetry: Send + Sync { ); } +/// Telemetry for Responses WebSocket transport. +pub trait WebsocketTelemetry: Send + Sync { + fn on_ws_request(&self, duration: Duration, error: Option<&ApiError>); + + fn on_ws_event( + &self, + result: &Result>, ApiError>, + duration: Duration, + ); +} + pub(crate) trait WithStatus { fn status(&self) -> StatusCode; } diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 1715a04142..66ed20a435 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -90,6 +90,7 @@ tokio = { workspace = true, features = [ "signal", ] } tokio-util = { workspace = true, features = ["rt"] } +tokio-tungstenite = { workspace = true } toml = { workspace = true } toml_edit = { workspace = true } tracing = { workspace = true, features = ["log"] } @@ -145,6 +146,10 @@ image = { workspace = true, features = ["jpeg", "png"] } maplit = { workspace = true } predicates = { workspace = true } pretty_assertions = { workspace = true } +opentelemetry_sdk = { workspace = true, features = [ + "experimental_metrics_custom_reader", + "metrics", +] } serial_test = { workspace = true } tempfile = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index f01c145e56..6564e86c49 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -21,6 +21,7 @@ use codex_api::ResponsesWebsocketClient as ApiWebSocketResponsesClient; use codex_api::ResponsesWebsocketConnection as ApiWebSocketConnection; use codex_api::SseTelemetry; use codex_api::TransportError; +use codex_api::WebsocketTelemetry; use codex_api::build_conversation_headers; use codex_api::common::Reasoning; use codex_api::common::ResponsesWsRequest; @@ -46,6 +47,8 @@ use reqwest::StatusCode; use serde_json::Value; use std::time::Duration; use tokio::sync::mpsc; +use tokio_tungstenite::tungstenite::Error; +use tokio_tungstenite::tungstenite::Message; use tracing::warn; use crate::AuthManager; @@ -451,9 +454,14 @@ impl ModelClientSession { if needs_new { let mut headers = options.extra_headers.clone(); headers.extend(build_conversation_headers(options.conversation_id.clone())); + let websocket_telemetry = self.build_websocket_telemetry(); let new_conn: ApiWebSocketConnection = ApiWebSocketResponsesClient::new(api_provider, api_auth) - .connect(headers, options.turn_state.clone()) + .connect( + headers, + options.turn_state.clone(), + Some(websocket_telemetry), + ) .await?; self.connection = Some(new_conn); } @@ -650,6 +658,13 @@ impl ModelClientSession { let sse_telemetry: Arc = telemetry; (request_telemetry, sse_telemetry) } + + /// Builds telemetry for the Responses API WebSocket transport. + fn build_websocket_telemetry(&self) -> Arc { + let telemetry = Arc::new(ApiTelemetry::new(self.state.otel_manager.clone())); + let websocket_telemetry: Arc = telemetry; + websocket_telemetry + } } impl ModelClient { @@ -849,3 +864,19 @@ impl SseTelemetry for ApiTelemetry { self.otel_manager.log_sse_event(result, duration); } } + +impl WebsocketTelemetry for ApiTelemetry { + fn on_ws_request(&self, duration: Duration, error: Option<&ApiError>) { + let error_message = error.map(std::string::ToString::to_string); + self.otel_manager + .record_websocket_request(duration, error_message.as_deref()); + } + + fn on_ws_event( + &self, + result: &std::result::Result>, ApiError>, + duration: Duration, + ) { + self.otel_manager.record_websocket_event(result, duration); + } +} diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 5037479987..fc0ff37cf4 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -14,6 +14,8 @@ use codex_core::features::Feature; use codex_core::models_manager::manager::ModelsManager; use codex_core::protocol::SessionSource; use codex_otel::OtelManager; +use codex_otel::metrics::MetricsClient; +use codex_otel::metrics::MetricsConfig; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary; use core_test_support::load_default_config_for_test; @@ -25,15 +27,19 @@ use core_test_support::responses::start_websocket_server; use core_test_support::responses::start_websocket_server_with_headers; use core_test_support::skip_if_no_network; use futures::StreamExt; +use opentelemetry_sdk::metrics::InMemoryMetricExporter; use pretty_assertions::assert_eq; use std::sync::Arc; +use std::time::Duration; use tempfile::TempDir; +use tracing_test::traced_test; const MODEL: &str = "gpt-5.2-codex"; struct WebsocketTestHarness { _codex_home: TempDir, client: ModelClient, + otel_manager: OtelManager, } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -64,6 +70,38 @@ async fn responses_websocket_streams_request() { server.shutdown().await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[traced_test] +async fn responses_websocket_emits_websocket_telemetry_events() { + skip_if_no_network!(); + + let server = start_websocket_server(vec![vec![vec![ + ev_response_created("resp-1"), + ev_completed("resp-1"), + ]]]) + .await; + + let harness = websocket_harness(&server).await; + harness.otel_manager.reset_runtime_metrics(); + let mut session = harness.client.new_session(); + let prompt = prompt_with_input(vec![message_item("hello")]); + + stream_until_complete(&mut session, &prompt).await; + + tokio::time::sleep(Duration::from_millis(10)).await; + + let summary = harness + .otel_manager + .runtime_metrics_summary() + .expect("runtime metrics summary"); + assert_eq!(summary.api_calls.count, 0); + assert_eq!(summary.streaming_events.count, 0); + assert_eq!(summary.websocket_calls.count, 1); + assert_eq!(summary.websocket_events.count, 2); + + server.shutdown().await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn responses_websocket_emits_reasoning_included_event() { skip_if_no_network!(); @@ -211,6 +249,12 @@ async fn websocket_harness(server: &WebSocketTestServer) -> WebsocketTestHarness let model_info = ModelsManager::construct_model_info_offline(MODEL, &config); let conversation_id = ThreadId::new(); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); + let exporter = InMemoryMetricExporter::default(); + let metrics = MetricsClient::new( + MetricsConfig::in_memory("test", "codex-core", env!("CARGO_PKG_VERSION"), exporter) + .with_runtime_reader(), + ) + .expect("in-memory metrics client"); let otel_manager = OtelManager::new( conversation_id, MODEL, @@ -221,12 +265,13 @@ async fn websocket_harness(server: &WebSocketTestServer) -> WebsocketTestHarness false, "test".to_string(), SessionSource::Exec, - ); + ) + .with_metrics(metrics); let client = ModelClient::new( Arc::clone(&config), None, model_info, - otel_manager, + otel_manager.clone(), provider.clone(), None, ReasoningSummary::Auto, @@ -238,6 +283,7 @@ async fn websocket_harness(server: &WebSocketTestServer) -> WebsocketTestHarness WebsocketTestHarness { _codex_home: codex_home, client, + otel_manager, } } diff --git a/codex-rs/otel/Cargo.toml b/codex-rs/otel/Cargo.toml index f9a54c53d9..0fbd2a8055 100644 --- a/codex-rs/otel/Cargo.toml +++ b/codex-rs/otel/Cargo.toml @@ -56,6 +56,7 @@ serde_json = { workspace = true } strum_macros = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } +tokio-tungstenite = { workspace = true } tracing = { workspace = true } tracing-opentelemetry = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/codex-rs/otel/src/metrics/names.rs b/codex-rs/otel/src/metrics/names.rs index 4db5dfb028..b8ff9364ad 100644 --- a/codex-rs/otel/src/metrics/names.rs +++ b/codex-rs/otel/src/metrics/names.rs @@ -4,3 +4,7 @@ pub(crate) const API_CALL_COUNT_METRIC: &str = "codex.api_request"; pub(crate) const API_CALL_DURATION_METRIC: &str = "codex.api_request.duration_ms"; pub(crate) const SSE_EVENT_COUNT_METRIC: &str = "codex.sse_event"; pub(crate) const SSE_EVENT_DURATION_METRIC: &str = "codex.sse_event.duration_ms"; +pub(crate) const WEBSOCKET_REQUEST_COUNT_METRIC: &str = "codex.websocket.request"; +pub(crate) const WEBSOCKET_REQUEST_DURATION_METRIC: &str = "codex.websocket.request.duration_ms"; +pub(crate) const WEBSOCKET_EVENT_COUNT_METRIC: &str = "codex.websocket.event"; +pub(crate) const WEBSOCKET_EVENT_DURATION_METRIC: &str = "codex.websocket.event.duration_ms"; diff --git a/codex-rs/otel/src/metrics/runtime_metrics.rs b/codex-rs/otel/src/metrics/runtime_metrics.rs index 98498a3532..dbd28010f6 100644 --- a/codex-rs/otel/src/metrics/runtime_metrics.rs +++ b/codex-rs/otel/src/metrics/runtime_metrics.rs @@ -4,6 +4,10 @@ use crate::metrics::names::SSE_EVENT_COUNT_METRIC; use crate::metrics::names::SSE_EVENT_DURATION_METRIC; use crate::metrics::names::TOOL_CALL_COUNT_METRIC; use crate::metrics::names::TOOL_CALL_DURATION_METRIC; +use crate::metrics::names::WEBSOCKET_EVENT_COUNT_METRIC; +use crate::metrics::names::WEBSOCKET_EVENT_DURATION_METRIC; +use crate::metrics::names::WEBSOCKET_REQUEST_COUNT_METRIC; +use crate::metrics::names::WEBSOCKET_REQUEST_DURATION_METRIC; use opentelemetry_sdk::metrics::data::AggregatedMetrics; use opentelemetry_sdk::metrics::data::Metric; use opentelemetry_sdk::metrics::data::MetricData; @@ -26,11 +30,17 @@ pub struct RuntimeMetricsSummary { pub tool_calls: RuntimeMetricTotals, pub api_calls: RuntimeMetricTotals, pub streaming_events: RuntimeMetricTotals, + pub websocket_calls: RuntimeMetricTotals, + pub websocket_events: RuntimeMetricTotals, } impl RuntimeMetricsSummary { pub fn is_empty(self) -> bool { - self.tool_calls.is_empty() && self.api_calls.is_empty() && self.streaming_events.is_empty() + self.tool_calls.is_empty() + && self.api_calls.is_empty() + && self.streaming_events.is_empty() + && self.websocket_calls.is_empty() + && self.websocket_events.is_empty() } pub(crate) fn from_snapshot(snapshot: &ResourceMetrics) -> Self { @@ -46,10 +56,20 @@ impl RuntimeMetricsSummary { count: sum_counter(snapshot, SSE_EVENT_COUNT_METRIC), duration_ms: sum_histogram_ms(snapshot, SSE_EVENT_DURATION_METRIC), }; + let websocket_calls = RuntimeMetricTotals { + count: sum_counter(snapshot, WEBSOCKET_REQUEST_COUNT_METRIC), + duration_ms: sum_histogram_ms(snapshot, WEBSOCKET_REQUEST_DURATION_METRIC), + }; + let websocket_events = RuntimeMetricTotals { + count: sum_counter(snapshot, WEBSOCKET_EVENT_COUNT_METRIC), + duration_ms: sum_histogram_ms(snapshot, WEBSOCKET_EVENT_DURATION_METRIC), + }; Self { tool_calls, api_calls, streaming_events, + websocket_calls, + websocket_events, } } } diff --git a/codex-rs/otel/src/traces/otel_manager.rs b/codex-rs/otel/src/traces/otel_manager.rs index 9a7744ee60..c585ec67d5 100644 --- a/codex-rs/otel/src/traces/otel_manager.rs +++ b/codex-rs/otel/src/traces/otel_manager.rs @@ -4,9 +4,14 @@ use crate::metrics::names::SSE_EVENT_COUNT_METRIC; use crate::metrics::names::SSE_EVENT_DURATION_METRIC; use crate::metrics::names::TOOL_CALL_COUNT_METRIC; use crate::metrics::names::TOOL_CALL_DURATION_METRIC; +use crate::metrics::names::WEBSOCKET_EVENT_COUNT_METRIC; +use crate::metrics::names::WEBSOCKET_EVENT_DURATION_METRIC; +use crate::metrics::names::WEBSOCKET_REQUEST_COUNT_METRIC; +use crate::metrics::names::WEBSOCKET_REQUEST_DURATION_METRIC; use crate::otel_provider::traceparent_context_from_env; use chrono::SecondsFormat; use chrono::Utc; +use codex_api::ApiError; use codex_api::ResponseEvent; use codex_app_server_protocol::AuthMode; use codex_protocol::ThreadId; @@ -36,6 +41,7 @@ pub use crate::OtelManager; pub use crate::ToolDecisionSource; const SSE_UNKNOWN_KIND: &str = "unknown"; +const WEBSOCKET_UNKNOWN_KIND: &str = "unknown"; impl OtelManager { #[allow(clippy::too_many_arguments)] @@ -190,6 +196,134 @@ impl OtelManager { ); } + pub fn record_websocket_request(&self, duration: Duration, error: Option<&str>) { + let success_str = if error.is_none() { "true" } else { "false" }; + self.counter( + WEBSOCKET_REQUEST_COUNT_METRIC, + 1, + &[("success", success_str)], + ); + self.record_duration( + WEBSOCKET_REQUEST_DURATION_METRIC, + duration, + &[("success", success_str)], + ); + tracing::event!( + tracing::Level::INFO, + event.name = "codex.websocket_request", + event.timestamp = %timestamp(), + conversation.id = %self.metadata.conversation_id, + app.version = %self.metadata.app_version, + auth_mode = self.metadata.auth_mode, + user.account_id = self.metadata.account_id, + user.email = self.metadata.account_email, + terminal.type = %self.metadata.terminal_type, + model = %self.metadata.model, + slug = %self.metadata.slug, + duration_ms = %duration.as_millis(), + success = success_str, + error.message = error, + ); + } + + pub fn record_websocket_event( + &self, + result: &Result< + Option< + Result< + tokio_tungstenite::tungstenite::Message, + tokio_tungstenite::tungstenite::Error, + >, + >, + ApiError, + >, + duration: Duration, + ) { + let mut kind = None; + let mut error_message = None; + let mut success = true; + + match result { + Ok(Some(Ok(message))) => match message { + tokio_tungstenite::tungstenite::Message::Text(text) => { + match serde_json::from_str::(text) { + Ok(value) => { + kind = value + .get("type") + .and_then(|value| value.as_str()) + .map(std::string::ToString::to_string); + if kind.as_deref() == Some("response.failed") { + success = false; + error_message = value + .get("response") + .and_then(|value| value.get("error")) + .map(serde_json::Value::to_string) + .or_else(|| Some("response.failed event received".to_string())); + } + } + Err(err) => { + kind = Some("parse_error".to_string()); + error_message = Some(err.to_string()); + success = false; + } + } + } + tokio_tungstenite::tungstenite::Message::Binary(_) => { + success = false; + error_message = Some("unexpected binary websocket event".to_string()); + } + tokio_tungstenite::tungstenite::Message::Ping(_) + | tokio_tungstenite::tungstenite::Message::Pong(_) => { + return; + } + tokio_tungstenite::tungstenite::Message::Close(_) => { + success = false; + error_message = + Some("websocket closed by server before response.completed".to_string()); + } + tokio_tungstenite::tungstenite::Message::Frame(_) => { + success = false; + error_message = Some("unexpected websocket frame".to_string()); + } + }, + Ok(Some(Err(err))) => { + success = false; + error_message = Some(err.to_string()); + } + Ok(None) => { + success = false; + error_message = Some("stream closed before response.completed".to_string()); + } + Err(err) => { + success = false; + error_message = Some(err.to_string()); + } + } + + let kind_str = kind.as_deref().unwrap_or(WEBSOCKET_UNKNOWN_KIND); + let success_str = if success { "true" } else { "false" }; + let tags = [("kind", kind_str), ("success", success_str)]; + self.counter(WEBSOCKET_EVENT_COUNT_METRIC, 1, &tags); + self.record_duration(WEBSOCKET_EVENT_DURATION_METRIC, duration, &tags); + tracing::event!( + tracing::Level::INFO, + event.name = "codex.websocket_event", + event.timestamp = %timestamp(), + event.kind = %kind_str, + conversation.id = %self.metadata.conversation_id, + app.version = %self.metadata.app_version, + auth_mode = self.metadata.auth_mode, + user.account_id = self.metadata.account_id, + user.email = self.metadata.account_email, + terminal.type = %self.metadata.terminal_type, + model = %self.metadata.model, + slug = %self.metadata.slug, + duration_ms = %duration.as_millis(), + success = success_str, + error.message = error_message.as_deref(), + ); + } + pub fn log_sse_event( &self, response: &Result>>, Elapsed>, diff --git a/codex-rs/otel/tests/suite/runtime_summary.rs b/codex-rs/otel/tests/suite/runtime_summary.rs index d51c40a9ed..78aed8a0a7 100644 --- a/codex-rs/otel/tests/suite/runtime_summary.rs +++ b/codex-rs/otel/tests/suite/runtime_summary.rs @@ -11,6 +11,7 @@ use eventsource_stream::Event as StreamEvent; use opentelemetry_sdk::metrics::InMemoryMetricExporter; use pretty_assertions::assert_eq; use std::time::Duration; +use tokio_tungstenite::tungstenite::Message; #[test] fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<()> { @@ -43,6 +44,7 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( "ok", ); manager.record_api_request(1, Some(200), None, Duration::from_millis(300)); + manager.record_websocket_request(Duration::from_millis(400), None); let sse_response: std::result::Result< Option>>, tokio::time::error::Elapsed, @@ -53,6 +55,13 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( retry: None, }))); manager.log_sse_event(&sse_response, Duration::from_millis(120)); + let ws_response: std::result::Result< + Option>, + codex_api::ApiError, + > = Ok(Some(Ok(Message::Text( + r#"{"type":"response.created"}"#.into(), + )))); + manager.record_websocket_event(&ws_response, Duration::from_millis(80)); let summary = manager .runtime_metrics_summary() @@ -70,6 +79,14 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( count: 1, duration_ms: 120, }, + websocket_calls: RuntimeMetricTotals { + count: 1, + duration_ms: 400, + }, + websocket_events: RuntimeMetricTotals { + count: 1, + duration_ms: 80, + }, }; assert_eq!(summary, expected); diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 826624889e..934c9506f5 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -2028,6 +2028,13 @@ fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { summary.api_calls.count )); } + if summary.websocket_calls.count > 0 { + let duration = format_duration_ms(summary.websocket_calls.duration_ms); + parts.push(format!( + "WebSocket: {} events send ({duration})", + summary.websocket_calls.count + )); + } if summary.streaming_events.count > 0 { let duration = format_duration_ms(summary.streaming_events.duration_ms); let stream_label = pluralize(summary.streaming_events.count, "Stream", "Streams"); @@ -2037,6 +2044,13 @@ fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { summary.streaming_events.count )); } + if summary.websocket_events.count > 0 { + let duration = format_duration_ms(summary.websocket_events.duration_ms); + parts.push(format!( + "{} events received ({duration})", + summary.websocket_events.count + )); + } if parts.is_empty() { None } else { @@ -2181,15 +2195,25 @@ mod tests { count: 6, duration_ms: 900, }, + websocket_calls: RuntimeMetricTotals { + count: 1, + duration_ms: 700, + }, + websocket_events: RuntimeMetricTotals { + count: 4, + duration_ms: 1_200, + }, }; let cell = FinalMessageSeparator::new(Some(12), Some(summary)); - let rendered = render_lines(&cell.display_lines(120)); + let rendered = render_lines(&cell.display_lines(200)); assert_eq!(rendered.len(), 1); assert!(rendered[0].contains("Worked for 12s")); assert!(rendered[0].contains("Local tools: 3 calls (2.5s)")); assert!(rendered[0].contains("Inference: 2 calls (1.2s)")); + assert!(rendered[0].contains("WebSocket: 1 events send (700ms)")); assert!(rendered[0].contains("Streams: 6 events (900ms)")); + assert!(rendered[0].contains("4 events received (1.2s)")); } #[test] From a33fa4bfe5742341e4ee9a15fed6157a9375fe25 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sat, 31 Jan 2026 20:38:06 -0700 Subject: [PATCH 049/139] chore(config) Rename config setting to personality (#10314) ## Summary Let's make the setting name consistent with the SlashCommand! ## Testing - [x] Updated tests --- .../app-server/src/codex_message_processor.rs | 2 +- codex-rs/core/config.schema.json | 24 +++++++++--------- codex-rs/core/src/codex.rs | 22 ++++++++-------- codex-rs/core/src/config/edit.rs | 4 +-- codex-rs/core/src/config/mod.rs | 25 +++++++++---------- codex-rs/core/src/config/profile.rs | 2 +- codex-rs/core/src/context_manager/history.rs | 2 +- codex-rs/core/src/personality_migration.rs | 10 ++++---- codex-rs/core/tests/suite/personality.rs | 22 ++++++++-------- .../core/tests/suite/personality_migration.rs | 4 +-- codex-rs/exec/src/lib.rs | 2 +- codex-rs/tui/src/app.rs | 4 +-- codex-rs/tui/src/chatwidget.rs | 9 +++---- 13 files changed, 64 insertions(+), 68 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index aae369a19d..76b2393e8a 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1770,7 +1770,7 @@ impl CodexMessageProcessor { codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.clone(), base_instructions, developer_instructions, - model_personality: personality, + personality, ..Default::default() } } diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index e847d13930..c487659681 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -261,9 +261,6 @@ ], "description": "Optional path to a file containing model instructions." }, - "model_personality": { - "$ref": "#/definitions/Personality" - }, "model_provider": { "description": "The key in the `model_providers` map identifying the [`ModelProviderInfo`] to use.", "type": "string" @@ -280,6 +277,9 @@ "oss_provider": { "type": "string" }, + "personality": { + "$ref": "#/definitions/Personality" + }, "sandbox_mode": { "$ref": "#/definitions/SandboxMode" }, @@ -1376,14 +1376,6 @@ ], "description": "Optional path to a file containing model instructions that will override the built-in instructions for the selected model. Users are STRONGLY DISCOURAGED from using this field, as deviating from the instructions sanctioned by Codex will likely degrade model performance." }, - "model_personality": { - "allOf": [ - { - "$ref": "#/definitions/Personality" - } - ], - "description": "EXPERIMENTAL Optionally specify a personality for the model" - }, "model_provider": { "description": "Provider to use from the model_providers map.", "type": "string" @@ -1442,6 +1434,14 @@ ], "description": "OTEL configuration." }, + "personality": { + "allOf": [ + { + "$ref": "#/definitions/Personality" + } + ], + "description": "Optionally specify a personality for the model" + }, "profile": { "description": "Profile to use from the `profiles` map.", "type": "string" @@ -1569,4 +1569,4 @@ }, "title": "ConfigToml", "type": "object" -} \ No newline at end of file +} diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index f987357874..29b02c006a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -332,7 +332,7 @@ impl Codex { .base_instructions .clone() .or_else(|| conversation_history.get_base_instructions().map(|s| s.text)) - .unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)); + .unwrap_or_else(|| model_info.get_model_instructions(config.personality)); // Respect explicit thread-start tools; fall back to persisted tools when resuming a thread. let dynamic_tools = if dynamic_tools.is_empty() { conversation_history.get_dynamic_tools().unwrap_or_default() @@ -356,7 +356,7 @@ impl Codex { model_reasoning_summary: config.model_reasoning_summary, developer_instructions: config.developer_instructions.clone(), user_instructions, - personality: config.model_personality, + personality: config.personality, base_instructions, compact_prompt: config.compact_prompt.clone(), approval_policy: config.approval_policy.clone(), @@ -634,7 +634,7 @@ impl Session { per_turn_config.model_reasoning_effort = session_configuration.collaboration_mode.reasoning_effort(); per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary; - per_turn_config.model_personality = session_configuration.personality; + per_turn_config.personality = session_configuration.personality; per_turn_config.web_search_mode = Some(resolve_web_search_mode_for_turn( per_turn_config.web_search_mode, session_configuration.provider.is_azure_responses_endpoint(), @@ -4975,11 +4975,11 @@ mod tests { model_reasoning_summary: config.model_reasoning_summary, developer_instructions: config.developer_instructions.clone(), user_instructions: config.user_instructions.clone(), - personality: config.model_personality, + personality: config.personality, base_instructions: config .base_instructions .clone() - .unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)), + .unwrap_or_else(|| model_info.get_model_instructions(config.personality)), compact_prompt: config.compact_prompt.clone(), approval_policy: config.approval_policy.clone(), sandbox_policy: config.sandbox_policy.clone(), @@ -5058,11 +5058,11 @@ mod tests { model_reasoning_summary: config.model_reasoning_summary, developer_instructions: config.developer_instructions.clone(), user_instructions: config.user_instructions.clone(), - personality: config.model_personality, + personality: config.personality, base_instructions: config .base_instructions .clone() - .unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)), + .unwrap_or_else(|| model_info.get_model_instructions(config.personality)), compact_prompt: config.compact_prompt.clone(), approval_policy: config.approval_policy.clone(), sandbox_policy: config.sandbox_policy.clone(), @@ -5325,11 +5325,11 @@ mod tests { model_reasoning_summary: config.model_reasoning_summary, developer_instructions: config.developer_instructions.clone(), user_instructions: config.user_instructions.clone(), - personality: config.model_personality, + personality: config.personality, base_instructions: config .base_instructions .clone() - .unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)), + .unwrap_or_else(|| model_info.get_model_instructions(config.personality)), compact_prompt: config.compact_prompt.clone(), approval_policy: config.approval_policy.clone(), sandbox_policy: config.sandbox_policy.clone(), @@ -5445,11 +5445,11 @@ mod tests { model_reasoning_summary: config.model_reasoning_summary, developer_instructions: config.developer_instructions.clone(), user_instructions: config.user_instructions.clone(), - personality: config.model_personality, + personality: config.personality, base_instructions: config .base_instructions .clone() - .unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)), + .unwrap_or_else(|| model_info.get_model_instructions(config.personality)), compact_prompt: config.compact_prompt.clone(), approval_policy: config.approval_policy.clone(), sandbox_policy: config.sandbox_policy.clone(), diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index a34e689348..cf87451821 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -278,7 +278,7 @@ impl ConfigDocument { mutated }), ConfigEdit::SetModelPersonality { personality } => Ok(self.write_profile_value( - &["model_personality"], + &["personality"], personality.map(|personality| value(personality.to_string())), )), ConfigEdit::SetNoticeHideFullAccessWarning(acknowledged) => Ok(self.write_value( @@ -724,7 +724,7 @@ impl ConfigEditsBuilder { self } - pub fn set_model_personality(mut self, personality: Option) -> Self { + pub fn set_personality(mut self, personality: Option) -> Self { self.edits .push(ConfigEdit::SetModelPersonality { personality }); self diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 23d7ef96e5..ffb0cd129e 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -134,7 +134,7 @@ pub struct Config { pub model_provider: ModelProviderInfo, /// Optionally specify the personality of the model - pub model_personality: Option, + pub personality: Option, /// Approval policy for executing commands. pub approval_policy: Constrained, @@ -912,9 +912,8 @@ pub struct ConfigToml { /// Override to force-enable reasoning summaries for the configured model. pub model_supports_reasoning_summaries: Option, - /// EXPERIMENTAL /// Optionally specify a personality for the model - pub model_personality: Option, + pub personality: Option, /// Base URL for requests to ChatGPT (as opposed to the OpenAI API). pub chatgpt_base_url: Option, @@ -1193,7 +1192,7 @@ pub struct ConfigOverrides { pub codex_linux_sandbox_exe: Option, pub base_instructions: Option, pub developer_instructions: Option, - pub model_personality: Option, + pub personality: Option, pub compact_prompt: Option, pub include_apply_patch_tool: Option, pub show_raw_agent_reasoning: Option, @@ -1300,7 +1299,7 @@ impl Config { codex_linux_sandbox_exe, base_instructions, developer_instructions, - model_personality, + personality, compact_prompt, include_apply_patch_tool: include_apply_patch_tool_override, show_raw_agent_reasoning, @@ -1497,9 +1496,9 @@ impl Config { Self::try_read_non_empty_file(model_instructions_path, "model instructions file")?; let base_instructions = base_instructions.or(file_base_instructions); let developer_instructions = developer_instructions.or(cfg.developer_instructions); - let model_personality = model_personality - .or(config_profile.model_personality) - .or(cfg.model_personality) + let personality = personality + .or(config_profile.personality) + .or(cfg.personality) .or_else(|| { features .enabled(Feature::Personality) @@ -1557,7 +1556,7 @@ impl Config { notify: cfg.notify, user_instructions, base_instructions, - model_personality, + personality, developer_instructions, compact_prompt, // The config.toml omits "_mode" because it's a config file. However, "_mode" @@ -3812,7 +3811,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::Detailed, model_supports_reasoning_summaries: None, model_verbosity: None, - model_personality: None, + personality: None, chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -3897,7 +3896,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: None, model_verbosity: None, - model_personality: None, + personality: None, chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -3997,7 +3996,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: None, model_verbosity: None, - model_personality: None, + personality: None, chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -4083,7 +4082,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::Detailed, model_supports_reasoning_summaries: None, model_verbosity: Some(Verbosity::High), - model_personality: None, + personality: None, chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, diff --git a/codex-rs/core/src/config/profile.rs b/codex-rs/core/src/config/profile.rs index e78b02374f..e2575fd868 100644 --- a/codex-rs/core/src/config/profile.rs +++ b/codex-rs/core/src/config/profile.rs @@ -25,7 +25,7 @@ pub struct ConfigProfile { pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, - pub model_personality: Option, + pub personality: Option, pub chatgpt_base_url: Option, /// Optional path to a file containing model instructions. pub model_instructions_file: Option, diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 1638a243cc..92dcef5ba4 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -88,7 +88,7 @@ impl ContextManager { let model_info = turn_context.client.get_model_info(); let personality = turn_context .personality - .or(turn_context.client.config().model_personality); + .or(turn_context.client.config().personality); let base_instructions = model_info.get_model_instructions(personality); let base_tokens = i64::try_from(approx_token_count(&base_instructions)).unwrap_or(i64::MAX); diff --git a/codex-rs/core/src/personality_migration.rs b/codex-rs/core/src/personality_migration.rs index cfe17c1711..4ec78696b6 100644 --- a/codex-rs/core/src/personality_migration.rs +++ b/codex-rs/core/src/personality_migration.rs @@ -36,7 +36,7 @@ pub async fn maybe_migrate_personality( let config_profile = config_toml .get_config_profile(None) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - if config_toml.model_personality.is_some() || config_profile.model_personality.is_some() { + if config_toml.personality.is_some() || config_profile.personality.is_some() { create_marker(&marker_path).await?; return Ok(PersonalityMigrationStatus::SkippedExplicitPersonality); } @@ -52,7 +52,7 @@ pub async fn maybe_migrate_personality( } ConfigEditsBuilder::new(codex_home) - .set_model_personality(Some(Personality::Pragmatic)) + .set_personality(Some(Personality::Pragmatic)) .apply() .await .map_err(|err| { @@ -211,7 +211,7 @@ mod tests { assert!(temp.path().join(PERSONALITY_MIGRATION_FILENAME).exists()); let persisted = read_config_toml(temp.path()).await?; - assert_eq!(persisted.model_personality, Some(Personality::Pragmatic)); + assert_eq!(persisted.personality, Some(Personality::Pragmatic)); Ok(()) } @@ -232,7 +232,7 @@ mod tests { async fn skips_when_personality_explicit() -> io::Result<()> { let temp = TempDir::new()?; ConfigEditsBuilder::new(temp.path()) - .set_model_personality(Some(Personality::Friendly)) + .set_personality(Some(Personality::Friendly)) .apply() .await .map_err(|err| io::Error::other(format!("failed to write config: {err}")))?; @@ -247,7 +247,7 @@ mod tests { assert!(temp.path().join(PERSONALITY_MIGRATION_FILENAME).exists()); let persisted = read_config_toml(temp.path()).await?; - assert_eq!(persisted.model_personality, Some(Personality::Friendly)); + assert_eq!(persisted.personality, Some(Personality::Friendly)); Ok(()) } diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index e4fe3371a0..2b00574d58 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -46,15 +46,15 @@ fn sse_completed(id: &str) -> String { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn model_personality_does_not_mutate_base_instructions_without_template() { +async fn personality_does_not_mutate_base_instructions_without_template() { let codex_home = TempDir::new().expect("create temp dir"); let mut config = load_default_config_for_test(&codex_home).await; config.features.enable(Feature::Personality); - config.model_personality = Some(Personality::Friendly); + config.personality = Some(Personality::Friendly); let model_info = ModelsManager::construct_model_info_offline("gpt-5.1", &config); assert_eq!( - model_info.get_model_instructions(config.model_personality), + model_info.get_model_instructions(config.personality), model_info.base_instructions ); } @@ -64,14 +64,14 @@ async fn base_instructions_override_disables_personality_template() { let codex_home = TempDir::new().expect("create temp dir"); let mut config = load_default_config_for_test(&codex_home).await; config.features.enable(Feature::Personality); - config.model_personality = Some(Personality::Friendly); + config.personality = Some(Personality::Friendly); config.base_instructions = Some("override instructions".to_string()); let model_info = ModelsManager::construct_model_info_offline("gpt-5.2-codex", &config); assert_eq!(model_info.base_instructions, "override instructions"); assert_eq!( - model_info.get_model_instructions(config.model_personality), + model_info.get_model_instructions(config.personality), "override instructions" ); } @@ -133,7 +133,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result< .with_config(|config| { config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); - config.model_personality = Some(Personality::Friendly); + config.personality = Some(Personality::Friendly); }); let test = builder.build(&server).await?; @@ -277,11 +277,11 @@ async fn instructions_uses_base_if_feature_disabled() -> anyhow::Result<()> { let codex_home = TempDir::new().expect("create temp dir"); let mut config = load_default_config_for_test(&codex_home).await; config.features.disable(Feature::Personality); - config.model_personality = Some(Personality::Friendly); + config.personality = Some(Personality::Friendly); let model_info = ModelsManager::construct_model_info_offline("gpt-5.2-codex", &config); assert_eq!( - model_info.get_model_instructions(config.model_personality), + model_info.get_model_instructions(config.personality), model_info.base_instructions ); @@ -378,7 +378,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()> } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn ignores_remote_model_personality_if_remote_models_disabled() -> anyhow::Result<()> { +async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::builder() @@ -439,7 +439,7 @@ async fn ignores_remote_model_personality_if_remote_models_disabled() -> anyhow: config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.model = Some(remote_slug.to_string()); - config.model_personality = Some(Personality::Friendly); + config.personality = Some(Personality::Friendly); }); let test = builder.build(&server).await?; @@ -554,7 +554,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow config.features.enable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.model = Some(remote_slug.to_string()); - config.model_personality = Some(Personality::Friendly); + config.personality = Some(Personality::Friendly); }); let test = builder.build(&server).await?; diff --git a/codex-rs/core/tests/suite/personality_migration.rs b/codex-rs/core/tests/suite/personality_migration.rs index 4431db2472..492b4fdd22 100644 --- a/codex-rs/core/tests/suite/personality_migration.rs +++ b/codex-rs/core/tests/suite/personality_migration.rs @@ -131,7 +131,7 @@ async fn no_marker_sessions_sets_personality() -> io::Result<()> { ); let persisted = read_config_toml(temp.path()).await?; - assert_eq!(persisted.model_personality, Some(Personality::Pragmatic)); + assert_eq!(persisted.personality, Some(Personality::Pragmatic)); Ok(()) } @@ -149,6 +149,6 @@ async fn no_marker_archived_sessions_sets_personality() -> io::Result<()> { ); let persisted = read_config_toml(temp.path()).await?; - assert_eq!(persisted.model_personality, Some(Personality::Pragmatic)); + assert_eq!(persisted.personality, Some(Personality::Pragmatic)); Ok(()) } diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index e0f39beb23..74f056ba4d 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -251,7 +251,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any codex_linux_sandbox_exe, base_instructions: None, developer_instructions: None, - model_personality: None, + personality: None, compact_prompt: None, include_apply_patch_tool: None, show_raw_agent_reasoning: oss.then_some(true), diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 7bbbb2f997..0e155fdc00 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1875,7 +1875,7 @@ impl App { let profile = self.active_profile.as_deref(); match ConfigEditsBuilder::new(&self.config.codex_home) .with_profile(profile) - .set_model_personality(Some(personality)) + .set_personality(Some(personality)) .apply() .await { @@ -2325,7 +2325,7 @@ impl App { } fn on_update_personality(&mut self, personality: Personality) { - self.config.model_personality = Some(personality); + self.config.personality = Some(personality); self.chat_widget.set_personality(personality); } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 1efd6b8e44..09a7d0fc65 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -3244,7 +3244,7 @@ impl ChatWidget { }; let personality = self .config - .model_personality + .personality .filter(|_| self.config.features.enabled(Feature::Personality)) .filter(|_| self.current_model_supports_personality()); let op = Op::UserTurn { @@ -3880,10 +3880,7 @@ impl ChatWidget { } fn open_personality_popup_for_current_model(&mut self) { - let current_personality = self - .config - .model_personality - .unwrap_or(Personality::Friendly); + let current_personality = self.config.personality.unwrap_or(Personality::Friendly); let personalities = [Personality::Friendly, Personality::Pragmatic]; let supports_personality = self.current_model_supports_personality(); @@ -5184,7 +5181,7 @@ impl ChatWidget { /// Set the personality in the widget's config copy. pub(crate) fn set_personality(&mut self, personality: Personality) { - self.config.model_personality = Some(personality); + self.config.personality = Some(personality); } /// Set the model in the widget's config copy and stored collaboration mode. From 11c912c4af54def828d32de6180b8ddda04c978b Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sat, 31 Jan 2026 21:32:32 -0700 Subject: [PATCH 050/139] chore(features) Personality => Stable (#10310) ## Summary Bump `/personality` to stable ## Testing - [x] unit tests pass --- codex-rs/core/src/config/mod.rs | 8 ++++---- codex-rs/core/src/features.rs | 8 ++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index ffb0cd129e..8a53f388b9 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -3811,7 +3811,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::Detailed, model_supports_reasoning_summaries: None, model_verbosity: None, - personality: None, + personality: Some(Personality::Friendly), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -3896,7 +3896,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: None, model_verbosity: None, - personality: None, + personality: Some(Personality::Friendly), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -3996,7 +3996,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: None, model_verbosity: None, - personality: None, + personality: Some(Personality::Friendly), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -4082,7 +4082,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::Detailed, model_supports_reasoning_summaries: None, model_verbosity: Some(Verbosity::High), - personality: None, + personality: Some(Personality::Friendly), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index e4b5c22b05..97fde03382 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -564,12 +564,8 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::Personality, key: "personality", - stage: Stage::Experimental { - name: "Personality", - menu_description: "Choose a communication style for Codex.", - announcement: "NEW: Pick a personality for Codex. Enable in /experimental!", - }, - default_enabled: false, + stage: Stage::Stable, + default_enabled: true, }, FeatureSpec { id: Feature::ResponsesWebsockets, From dfba95309f230d6e38cf86c1e3b8d01f4c710b68 Mon Sep 17 00:00:00 2001 From: Gav Verma Date: Sat, 31 Jan 2026 20:44:18 -0800 Subject: [PATCH 051/139] Sync system skills from public repo (#10320) Syncs the system skills included in Codex with the updates in https://github.com/openai/skills/tree/main/skills/.system --- .../assets/samples/skill-creator/SKILL.md | 54 +++++---- .../skill-creator/scripts/init_skill.py | 27 ++++- .../skill-creator/scripts/package_skill.py | 111 ------------------ .../assets/samples/skill-installer/SKILL.md | 12 +- .../scripts/list-curated-skills.py | 103 ---------------- 5 files changed, 58 insertions(+), 249 deletions(-) delete mode 100644 codex-rs/core/src/skills/assets/samples/skill-creator/scripts/package_skill.py delete mode 100755 codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-curated-skills.py diff --git a/codex-rs/core/src/skills/assets/samples/skill-creator/SKILL.md b/codex-rs/core/src/skills/assets/samples/skill-creator/SKILL.md index 60251f16a6..4c0220dd44 100644 --- a/codex-rs/core/src/skills/assets/samples/skill-creator/SKILL.md +++ b/codex-rs/core/src/skills/assets/samples/skill-creator/SKILL.md @@ -11,7 +11,7 @@ This skill provides guidance for creating effective skills. ## About Skills -Skills are modular, self-contained packages that extend Codex's capabilities by providing +Skills are modular, self-contained folders that extend Codex's capabilities by providing specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific domains or tasks—they transform Codex from a general-purpose agent into a specialized agent equipped with procedural knowledge that no model can fully possess. @@ -56,6 +56,8 @@ skill-name/ │ │ ├── name: (required) │ │ └── description: (required) │ └── Markdown instructions (required) +├── agents/ (recommended) +│ └── openai.yaml - UI metadata for skill lists and chips └── Bundled Resources (optional) ├── scripts/ - Executable code (Python/Bash/etc.) ├── references/ - Documentation intended to be loaded into context as needed @@ -69,6 +71,16 @@ Every SKILL.md consists of: - **Frontmatter** (YAML): Contains `name` and `description` fields. These are the only fields that Codex reads to determine when the skill gets used, thus it is very important to be clear and comprehensive in describing what the skill is, and when it should be used. - **Body** (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all). +#### Agents metadata (recommended) + +- UI-facing metadata for skill lists and chips +- Read references/openai_yaml.md before generating values and follow its descriptions and constraints +- Create: human-facing `display_name`, `short_description`, and `default_prompt` by reading the skill +- Generate deterministically by passing the values as `--interface key=value` to `scripts/generate_openai_yaml.py` or `scripts/init_skill.py` +- On updates: validate `agents/openai.yaml` still matches SKILL.md; regenerate if stale +- Only include other optional interface fields (icons, brand color) if explicitly provided +- See references/openai_yaml.md for field definitions and examples + #### Bundled Resources (optional) ##### Scripts (`scripts/`) @@ -208,7 +220,7 @@ Skill creation involves these steps: 2. Plan reusable skill contents (scripts, references, assets) 3. Initialize the skill (run init_skill.py) 4. Edit the skill (implement resources and write SKILL.md) -5. Package the skill (run package_skill.py) +5. Validate the skill (run quick_validate.py) 6. Iterate based on real usage Follow these steps in order, skipping only if there is a clear reason why they are not applicable. @@ -266,7 +278,7 @@ To establish the skill's contents, analyze each concrete example to create a lis At this point, it is time to actually create the skill. -Skip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step. +Skip this step only if the skill being developed already exists. In this case, continue to the next step. When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable. @@ -288,11 +300,20 @@ The script: - Creates the skill directory at the specified path - Generates a SKILL.md template with proper frontmatter and TODO placeholders +- Creates `agents/openai.yaml` using agent-generated `display_name`, `short_description`, and `default_prompt` passed via `--interface key=value` - Optionally creates resource directories based on `--resources` - Optionally adds example files when `--examples` is set After initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files. +Generate `display_name`, `short_description`, and `default_prompt` by reading the skill, then pass them as `--interface key=value` to `init_skill.py` or regenerate with: + +```bash +scripts/generate_openai_yaml.py --interface key=value +``` + +Only include other optional interface fields when the user explicitly provides them. For full field descriptions and examples, see references/openai_yaml.md. + ### Step 4: Edit the Skill When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Codex to use. Include information that would be beneficial and non-obvious to Codex. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Codex instance execute these tasks more effectively. @@ -328,40 +349,21 @@ Write the YAML frontmatter with `name` and `description`: - Include all "when to use" information here - Not in the body. The body is only loaded after triggering, so "When to Use This Skill" sections in the body are not helpful to Codex. - Example description for a `docx` skill: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Codex needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks" -Ensure the frontmatter is valid YAML. Keep `name` and `description` as single-line scalars. If either could be interpreted as YAML syntax, wrap it in quotes. - Do not include any other fields in YAML frontmatter. ##### Body Write instructions for using the skill and its bundled resources. -### Step 5: Packaging a Skill +### Step 5: Validate the Skill -Once development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements: +Once development of the skill is complete, validate the skill folder to catch basic issues early: ```bash -scripts/package_skill.py +scripts/quick_validate.py ``` -Optional output directory specification: - -```bash -scripts/package_skill.py ./dist -``` - -The packaging script will: - -1. **Validate** the skill automatically, checking: - - - YAML frontmatter format and required fields - - Skill naming conventions and directory structure - - Description completeness and quality - - File organization and resource references - -2. **Package** the skill if validation passes, creating a .skill file named after the skill (e.g., `my-skill.skill`) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension. - -If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again. +The validation script checks YAML frontmatter format, required fields, and naming rules. If validation fails, fix the reported issues and run the command again. ### Step 6: Iterate diff --git a/codex-rs/core/src/skills/assets/samples/skill-creator/scripts/init_skill.py b/codex-rs/core/src/skills/assets/samples/skill-creator/scripts/init_skill.py index 8633fe9e3f..f90703eca8 100644 --- a/codex-rs/core/src/skills/assets/samples/skill-creator/scripts/init_skill.py +++ b/codex-rs/core/src/skills/assets/samples/skill-creator/scripts/init_skill.py @@ -3,13 +3,14 @@ Skill Initializer - Creates a new skill from template Usage: - init_skill.py --path [--resources scripts,references,assets] [--examples] + init_skill.py --path [--resources scripts,references,assets] [--examples] [--interface key=value] Examples: init_skill.py my-new-skill --path skills/public init_skill.py my-new-skill --path skills/public --resources scripts,references init_skill.py my-api-helper --path skills/private --resources scripts --examples init_skill.py custom-skill --path /custom/location + init_skill.py my-skill --path skills/public --interface short_description="Short UI label" """ import argparse @@ -17,6 +18,8 @@ import re import sys from pathlib import Path +from generate_openai_yaml import write_openai_yaml + MAX_SKILL_NAME_LENGTH = 64 ALLOWED_RESOURCES = {"scripts", "references", "assets"} @@ -252,7 +255,7 @@ def create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_ print("[OK] Created assets/") -def init_skill(skill_name, path, resources, include_examples): +def init_skill(skill_name, path, resources, include_examples, interface_overrides): """ Initialize a new skill directory with template SKILL.md. @@ -293,6 +296,15 @@ def init_skill(skill_name, path, resources, include_examples): print(f"[ERROR] Error creating SKILL.md: {e}") return None + # Create agents/openai.yaml + try: + result = write_openai_yaml(skill_dir, skill_name, interface_overrides) + if not result: + return None + except Exception as e: + print(f"[ERROR] Error creating agents/openai.yaml: {e}") + return None + # Create resource directories if requested if resources: try: @@ -312,7 +324,8 @@ def init_skill(skill_name, path, resources, include_examples): print("2. Add resources to scripts/, references/, and assets/ as needed") else: print("2. Create resource directories only if needed (scripts/, references/, assets/)") - print("3. Run the validator when ready to check the skill structure") + print("3. Update agents/openai.yaml if the UI metadata should differ") + print("4. Run the validator when ready to check the skill structure") return skill_dir @@ -333,6 +346,12 @@ def main(): action="store_true", help="Create example files inside the selected resource directories", ) + parser.add_argument( + "--interface", + action="append", + default=[], + help="Interface override in key=value format (repeatable)", + ) args = parser.parse_args() raw_skill_name = args.skill_name @@ -366,7 +385,7 @@ def main(): print(" Resources: none (create as needed)") print() - result = init_skill(skill_name, path, resources, args.examples) + result = init_skill(skill_name, path, resources, args.examples, args.interface) if result: sys.exit(0) diff --git a/codex-rs/core/src/skills/assets/samples/skill-creator/scripts/package_skill.py b/codex-rs/core/src/skills/assets/samples/skill-creator/scripts/package_skill.py deleted file mode 100644 index 9a039958bb..0000000000 --- a/codex-rs/core/src/skills/assets/samples/skill-creator/scripts/package_skill.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -""" -Skill Packager - Creates a distributable .skill file of a skill folder - -Usage: - python utils/package_skill.py [output-directory] - -Example: - python utils/package_skill.py skills/public/my-skill - python utils/package_skill.py skills/public/my-skill ./dist -""" - -import sys -import zipfile -from pathlib import Path - -from quick_validate import validate_skill - - -def package_skill(skill_path, output_dir=None): - """ - Package a skill folder into a .skill file. - - Args: - skill_path: Path to the skill folder - output_dir: Optional output directory for the .skill file (defaults to current directory) - - Returns: - Path to the created .skill file, or None if error - """ - skill_path = Path(skill_path).resolve() - - # Validate skill folder exists - if not skill_path.exists(): - print(f"[ERROR] Skill folder not found: {skill_path}") - return None - - if not skill_path.is_dir(): - print(f"[ERROR] Path is not a directory: {skill_path}") - return None - - # Validate SKILL.md exists - skill_md = skill_path / "SKILL.md" - if not skill_md.exists(): - print(f"[ERROR] SKILL.md not found in {skill_path}") - return None - - # Run validation before packaging - print("Validating skill...") - valid, message = validate_skill(skill_path) - if not valid: - print(f"[ERROR] Validation failed: {message}") - print(" Please fix the validation errors before packaging.") - return None - print(f"[OK] {message}\n") - - # Determine output location - skill_name = skill_path.name - if output_dir: - output_path = Path(output_dir).resolve() - output_path.mkdir(parents=True, exist_ok=True) - else: - output_path = Path.cwd() - - skill_filename = output_path / f"{skill_name}.skill" - - # Create the .skill file (zip format) - try: - with zipfile.ZipFile(skill_filename, "w", zipfile.ZIP_DEFLATED) as zipf: - # Walk through the skill directory - for file_path in skill_path.rglob("*"): - if file_path.is_file(): - # Calculate the relative path within the zip - arcname = file_path.relative_to(skill_path.parent) - zipf.write(file_path, arcname) - print(f" Added: {arcname}") - - print(f"\n[OK] Successfully packaged skill to: {skill_filename}") - return skill_filename - - except Exception as e: - print(f"[ERROR] Error creating .skill file: {e}") - return None - - -def main(): - if len(sys.argv) < 2: - print("Usage: python utils/package_skill.py [output-directory]") - print("\nExample:") - print(" python utils/package_skill.py skills/public/my-skill") - print(" python utils/package_skill.py skills/public/my-skill ./dist") - sys.exit(1) - - skill_path = sys.argv[1] - output_dir = sys.argv[2] if len(sys.argv) > 2 else None - - print(f"Packaging skill: {skill_path}") - if output_dir: - print(f" Output directory: {output_dir}") - print() - - result = package_skill(skill_path, output_dir) - - if result: - sys.exit(0) - else: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/codex-rs/core/src/skills/assets/samples/skill-installer/SKILL.md b/codex-rs/core/src/skills/assets/samples/skill-installer/SKILL.md index 857c32d0fe..313626ac2f 100644 --- a/codex-rs/core/src/skills/assets/samples/skill-installer/SKILL.md +++ b/codex-rs/core/src/skills/assets/samples/skill-installer/SKILL.md @@ -7,10 +7,10 @@ metadata: # Skill Installer -Helps install skills. By default these are from https://github.com/openai/skills/tree/main/skills/.curated, but users can also provide other locations. +Helps install skills. By default these are from https://github.com/openai/skills/tree/main/skills/.curated, but users can also provide other locations. Experimental skills live in https://github.com/openai/skills/tree/main/skills/.experimental and can be installed the same way. Use the helper scripts based on the task: -- List curated skills when the user asks what is available, or if the user uses this skill without specifying what to do. +- List skills when the user asks what is available, or if the user uses this skill without specifying what to do. Default listing is `.curated`, but you can pass `--path skills/.experimental` when they ask about experimental skills. - Install from the curated list when the user provides a skill name. - Install from another repo when the user provides a GitHub repo/path (including private repos). @@ -18,7 +18,7 @@ Install skills with the helper scripts. ## Communication -When listing curated skills, output approximately as follows, depending on the context of the user's request: +When listing skills, output approximately as follows, depending on the context of the user's request. If they ask about experimental skills, list from `.experimental` instead of `.curated` and label the source accordingly: """ Skills from {repo}: 1. skill-1 @@ -33,10 +33,12 @@ After installing a skill, tell the user: "Restart Codex to pick up new skills." All of these scripts use network, so when running in the sandbox, request escalation when running them. -- `scripts/list-curated-skills.py` (prints curated list with installed annotations) -- `scripts/list-curated-skills.py --format json` +- `scripts/list-skills.py` (prints skills list with installed annotations) +- `scripts/list-skills.py --format json` +- Example (experimental list): `scripts/list-skills.py --path skills/.experimental` - `scripts/install-skill-from-github.py --repo / --path [ ...]` - `scripts/install-skill-from-github.py --url https://github.com///tree//` +- Example (experimental skill): `scripts/install-skill-from-github.py --repo openai/skills --path skills/.experimental/` ## Behavior and Options diff --git a/codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-curated-skills.py b/codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-curated-skills.py deleted file mode 100755 index 08d475c8ae..0000000000 --- a/codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-curated-skills.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -"""List curated skills from a GitHub repo path.""" - -from __future__ import annotations - -import argparse -import json -import os -import sys -import urllib.error - -from github_utils import github_api_contents_url, github_request - -DEFAULT_REPO = "openai/skills" -DEFAULT_PATH = "skills/.curated" -DEFAULT_REF = "main" - - -class ListError(Exception): - pass - - -class Args(argparse.Namespace): - repo: str - path: str - ref: str - format: str - - -def _request(url: str) -> bytes: - return github_request(url, "codex-skill-list") - - -def _codex_home() -> str: - return os.environ.get("CODEX_HOME", os.path.expanduser("~/.codex")) - - -def _installed_skills() -> set[str]: - root = os.path.join(_codex_home(), "skills") - if not os.path.isdir(root): - return set() - entries = set() - for name in os.listdir(root): - path = os.path.join(root, name) - if os.path.isdir(path): - entries.add(name) - return entries - - -def _list_curated(repo: str, path: str, ref: str) -> list[str]: - api_url = github_api_contents_url(repo, path, ref) - try: - payload = _request(api_url) - except urllib.error.HTTPError as exc: - if exc.code == 404: - raise ListError( - "Curated skills path not found: " - f"https://github.com/{repo}/tree/{ref}/{path}" - ) from exc - raise ListError(f"Failed to fetch curated skills: HTTP {exc.code}") from exc - data = json.loads(payload.decode("utf-8")) - if not isinstance(data, list): - raise ListError("Unexpected curated listing response.") - skills = [item["name"] for item in data if item.get("type") == "dir"] - return sorted(skills) - - -def _parse_args(argv: list[str]) -> Args: - parser = argparse.ArgumentParser(description="List curated skills.") - parser.add_argument("--repo", default=DEFAULT_REPO) - parser.add_argument("--path", default=DEFAULT_PATH) - parser.add_argument("--ref", default=DEFAULT_REF) - parser.add_argument( - "--format", - choices=["text", "json"], - default="text", - help="Output format", - ) - return parser.parse_args(argv, namespace=Args()) - - -def main(argv: list[str]) -> int: - args = _parse_args(argv) - try: - skills = _list_curated(args.repo, args.path, args.ref) - installed = _installed_skills() - if args.format == "json": - payload = [ - {"name": name, "installed": name in installed} for name in skills - ] - print(json.dumps(payload)) - else: - for idx, name in enumerate(skills, start=1): - suffix = " (already installed)" if name in installed else "" - print(f"{idx}. {name}{suffix}") - return 0 - except ListError as exc: - print(f"Error: {exc}", file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) From e470461a9626b68092d7b0fd04274f9f0d4bb669 Mon Sep 17 00:00:00 2001 From: Gav Verma Date: Sat, 31 Jan 2026 21:07:35 -0800 Subject: [PATCH 052/139] Sync system skills from public repo for openai yaml changes (#10322) Follow-up to https://github.com/openai/codex/pull/10320 Syncing additional changes from https://github.com/openai/skills/tree/main/skills/.system --- .../samples/skill-creator/agents/openai.yaml | 5 + .../assets/skill-creator-small.svg | 3 + .../skill-creator/assets/skill-creator.png | Bin 0 -> 1563 bytes .../skill-creator/references/openai_yaml.md | 43 ++++ .../scripts/generate_openai_yaml.py | 225 ++++++++++++++++++ .../skill-installer/agents/openai.yaml | 5 + .../assets/skill-installer-small.svg | 3 + .../assets/skill-installer.png | Bin 0 -> 1086 bytes .../skill-installer/scripts/list-skills.py | 107 +++++++++ 9 files changed, 391 insertions(+) create mode 100644 codex-rs/core/src/skills/assets/samples/skill-creator/agents/openai.yaml create mode 100644 codex-rs/core/src/skills/assets/samples/skill-creator/assets/skill-creator-small.svg create mode 100644 codex-rs/core/src/skills/assets/samples/skill-creator/assets/skill-creator.png create mode 100644 codex-rs/core/src/skills/assets/samples/skill-creator/references/openai_yaml.md create mode 100644 codex-rs/core/src/skills/assets/samples/skill-creator/scripts/generate_openai_yaml.py create mode 100644 codex-rs/core/src/skills/assets/samples/skill-installer/agents/openai.yaml create mode 100644 codex-rs/core/src/skills/assets/samples/skill-installer/assets/skill-installer-small.svg create mode 100644 codex-rs/core/src/skills/assets/samples/skill-installer/assets/skill-installer.png create mode 100755 codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-skills.py diff --git a/codex-rs/core/src/skills/assets/samples/skill-creator/agents/openai.yaml b/codex-rs/core/src/skills/assets/samples/skill-creator/agents/openai.yaml new file mode 100644 index 0000000000..3095c600ce --- /dev/null +++ b/codex-rs/core/src/skills/assets/samples/skill-creator/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Skill Creator" + short_description: "Create or update a skill" + icon_small: "./assets/skill-creator-small.svg" + icon_large: "./assets/skill-creator.png" diff --git a/codex-rs/core/src/skills/assets/samples/skill-creator/assets/skill-creator-small.svg b/codex-rs/core/src/skills/assets/samples/skill-creator/assets/skill-creator-small.svg new file mode 100644 index 0000000000..c6e4f67c62 --- /dev/null +++ b/codex-rs/core/src/skills/assets/samples/skill-creator/assets/skill-creator-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/codex-rs/core/src/skills/assets/samples/skill-creator/assets/skill-creator.png b/codex-rs/core/src/skills/assets/samples/skill-creator/assets/skill-creator.png new file mode 100644 index 0000000000000000000000000000000000000000..4f3d6d82fa78fbdce97af3c17f6a25c683aa3290 GIT binary patch literal 1563 zcmV+$2ITpPP)1#i4F!Dtb@FnH%p6OalPi4TDy zMcTdqNqhk%K`*Qbx>_X{K4hvjHd<=hH0fq{Gsiib9J_g*IrFfyGygA?&1Rpl>Iw2qhkb ze|)YB#{A1DHe~H{=}oVwFPPA*37~O^y*^+fpb`thKY1$)zGE1LrVQMsu}jdkKwuIF z!Y>Y|aerZBkl8tR2W{Vc1+7>_fXyPMKjmfh7264^#1Q8f8yfM)Gsj@1QV6g#maH@I zP%I~8Ek1&*Sz<4`O%n=C#xm?`ka z@f*eCnG*Imx=o;)FU8GPEG9%?fE(jGa4*wlOu)drquT{LDa8yFs(BEJAz$>R*gPVE zqU#+{sHQ>IV#p$cqEO9(Y=$ALItt^-S@v#+A*(J*LO`5i$f|}?GICI;ra;_c$ihcq zX6#?7t76DPMwx(OH38xkLlzp!uBppVEb<_JVaNhUVVGdAj!ZzYNQ3x|AuFRSya%H+ z?dbZhQ0jjifQKRr5)?-HzW@Ca#_cg2y7h{`C>$TMzDr80D_JB#g2uq?7v-;0HimyF z%!H*6!ecrjR#+G^-I_yvejNAeo`a<|YBMrB2#6*u;OYX9V;b-SyY zmSuIfx3`<{v8qoM4|80YXzgSg7L69b@21RF+`z))FOtR$uxAc?!1W*3JruXoD~WPVK^^v5W;X* zpdf_dzQ8~TL#;qS2tz&JK?uWJzP@9~zdK179`pAt+{P25gyAjUK={;^Ves}2U?==6 zNf;tu_96V2E4^TR<-qwx`(A%qKJ|LP3BwTiGa(EWhXUIZ!jHo1q$CkT~d#9UdUR zH40%o$QuZQi$i}mMj?#VDT6HUKah5JtJNrku|D+)DV1De$d*y~ogWBegVa9;v@4x` z{E^m^dv>Y3)-b^IHelgvz;bpZlf!BnL!=DS*wpgU)oZ_Yc0Tbalu)#Ku-%=0qSQQw zNPQo|`ICR&zKgduf@(L>gD6zp1DQ&W{*=*XftU-Bx z{{AAaUi%GI*nGtbL!>kzrBZ3#>yIi-O6)L1N+5D`wYCsZVu>MA1<7o7SqLbx#Skfh zh!xgWmj{RvYYdUEo2<$LK#4tu$n`2~?FwtF{91jn{I@yA@4gUs43X;z*=U8eRh}nB zZ@!E}7h*pdGS?vX=F0UC71dDKEG$029y`&?4tIz02f3n9Xus>kIZ7E~2%2`79eMr( zBrMmT%M$w#UDy9A6bf}v=-|G+IQzzn<BbTJjk&~1^x!BXwGidKmHbOD6^2FPh=j>?` zzdy [--name ] [--interface key=value] +""" + +import argparse +import re +import sys +from pathlib import Path + +import yaml + +ACRONYMS = { + "GH", + "MCP", + "API", + "CI", + "CLI", + "LLM", + "PDF", + "PR", + "UI", + "URL", + "SQL", +} + +BRANDS = { + "openai": "OpenAI", + "openapi": "OpenAPI", + "github": "GitHub", + "pagerduty": "PagerDuty", + "datadog": "DataDog", + "sqlite": "SQLite", + "fastapi": "FastAPI", +} + +SMALL_WORDS = {"and", "or", "to", "up", "with"} + +ALLOWED_INTERFACE_KEYS = { + "display_name", + "short_description", + "icon_small", + "icon_large", + "brand_color", + "default_prompt", +} + + +def yaml_quote(value): + escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + return f'"{escaped}"' + + +def format_display_name(skill_name): + words = [word for word in skill_name.split("-") if word] + formatted = [] + for index, word in enumerate(words): + lower = word.lower() + upper = word.upper() + if upper in ACRONYMS: + formatted.append(upper) + continue + if lower in BRANDS: + formatted.append(BRANDS[lower]) + continue + if index > 0 and lower in SMALL_WORDS: + formatted.append(lower) + continue + formatted.append(word.capitalize()) + return " ".join(formatted) + + +def generate_short_description(display_name): + description = f"Help with {display_name} tasks" + + if len(description) < 25: + description = f"Help with {display_name} tasks and workflows" + if len(description) < 25: + description = f"Help with {display_name} tasks with guidance" + + if len(description) > 64: + description = f"Help with {display_name}" + if len(description) > 64: + description = f"{display_name} helper" + if len(description) > 64: + description = f"{display_name} tools" + if len(description) > 64: + suffix = " helper" + max_name_length = 64 - len(suffix) + trimmed = display_name[:max_name_length].rstrip() + description = f"{trimmed}{suffix}" + if len(description) > 64: + description = description[:64].rstrip() + + if len(description) < 25: + description = f"{description} workflows" + if len(description) > 64: + description = description[:64].rstrip() + + return description + + +def read_frontmatter_name(skill_dir): + skill_md = Path(skill_dir) / "SKILL.md" + if not skill_md.exists(): + print(f"[ERROR] SKILL.md not found in {skill_dir}") + return None + content = skill_md.read_text() + match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) + if not match: + print("[ERROR] Invalid SKILL.md frontmatter format.") + return None + frontmatter_text = match.group(1) + try: + frontmatter = yaml.safe_load(frontmatter_text) + except yaml.YAMLError as exc: + print(f"[ERROR] Invalid YAML frontmatter: {exc}") + return None + if not isinstance(frontmatter, dict): + print("[ERROR] Frontmatter must be a YAML dictionary.") + return None + name = frontmatter.get("name", "") + if not isinstance(name, str) or not name.strip(): + print("[ERROR] Frontmatter 'name' is missing or invalid.") + return None + return name.strip() + + +def parse_interface_overrides(raw_overrides): + overrides = {} + optional_order = [] + for item in raw_overrides: + if "=" not in item: + print(f"[ERROR] Invalid interface override '{item}'. Use key=value.") + return None, None + key, value = item.split("=", 1) + key = key.strip() + value = value.strip() + if not key: + print(f"[ERROR] Invalid interface override '{item}'. Key is empty.") + return None, None + if key not in ALLOWED_INTERFACE_KEYS: + allowed = ", ".join(sorted(ALLOWED_INTERFACE_KEYS)) + print(f"[ERROR] Unknown interface field '{key}'. Allowed: {allowed}") + return None, None + overrides[key] = value + if key not in ("display_name", "short_description") and key not in optional_order: + optional_order.append(key) + return overrides, optional_order + + +def write_openai_yaml(skill_dir, skill_name, raw_overrides): + overrides, optional_order = parse_interface_overrides(raw_overrides) + if overrides is None: + return None + + display_name = overrides.get("display_name") or format_display_name(skill_name) + short_description = overrides.get("short_description") or generate_short_description(display_name) + + if not (25 <= len(short_description) <= 64): + print( + "[ERROR] short_description must be 25-64 characters " + f"(got {len(short_description)})." + ) + return None + + interface_lines = [ + "interface:", + f" display_name: {yaml_quote(display_name)}", + f" short_description: {yaml_quote(short_description)}", + ] + + for key in optional_order: + value = overrides.get(key) + if value is not None: + interface_lines.append(f" {key}: {yaml_quote(value)}") + + agents_dir = Path(skill_dir) / "agents" + agents_dir.mkdir(parents=True, exist_ok=True) + output_path = agents_dir / "openai.yaml" + output_path.write_text("\n".join(interface_lines) + "\n") + print(f"[OK] Created agents/openai.yaml") + return output_path + + +def main(): + parser = argparse.ArgumentParser( + description="Create agents/openai.yaml for a skill directory.", + ) + parser.add_argument("skill_dir", help="Path to the skill directory") + parser.add_argument( + "--name", + help="Skill name override (defaults to SKILL.md frontmatter)", + ) + parser.add_argument( + "--interface", + action="append", + default=[], + help="Interface override in key=value format (repeatable)", + ) + args = parser.parse_args() + + skill_dir = Path(args.skill_dir).resolve() + if not skill_dir.exists(): + print(f"[ERROR] Skill directory not found: {skill_dir}") + sys.exit(1) + if not skill_dir.is_dir(): + print(f"[ERROR] Path is not a directory: {skill_dir}") + sys.exit(1) + + skill_name = args.name or read_frontmatter_name(skill_dir) + if not skill_name: + sys.exit(1) + + result = write_openai_yaml(skill_dir, skill_name, args.interface) + if result: + sys.exit(0) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/codex-rs/core/src/skills/assets/samples/skill-installer/agents/openai.yaml b/codex-rs/core/src/skills/assets/samples/skill-installer/agents/openai.yaml new file mode 100644 index 0000000000..88d40cd946 --- /dev/null +++ b/codex-rs/core/src/skills/assets/samples/skill-installer/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Skill Installer" + short_description: "Install curated skills from openai/skills or other repos" + icon_small: "./assets/skill-installer-small.svg" + icon_large: "./assets/skill-installer.png" diff --git a/codex-rs/core/src/skills/assets/samples/skill-installer/assets/skill-installer-small.svg b/codex-rs/core/src/skills/assets/samples/skill-installer/assets/skill-installer-small.svg new file mode 100644 index 0000000000..ccfc034241 --- /dev/null +++ b/codex-rs/core/src/skills/assets/samples/skill-installer/assets/skill-installer-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/codex-rs/core/src/skills/assets/samples/skill-installer/assets/skill-installer.png b/codex-rs/core/src/skills/assets/samples/skill-installer/assets/skill-installer.png new file mode 100644 index 0000000000000000000000000000000000000000..2977cd5bb49b3b8bd50d8bf476d7cffbb3f88a46 GIT binary patch literal 1086 zcmV-E1i|}>P)6qd}PI zU}1Q*8dV$h?`gQj1R5vJPv45w5fz~cLLuCh0o2wr7yk^=5sDx*5-Ll>Xp|j99s4;% zQVhaANX@%UNr^#N2cgm7dK0)Ltb@2~8A!uANPWK#(lEbd$(cJ^cn7KaK4A_$))6Sba%Y@i-cF@4&0#eXMdBwS%I!9;%>MLUm$j zrCd1PXrVVcuxk8{|NH&3wJ zwpj5xtS;6mPHqVC8|_7OywPK))2M818jm9KAqOWj&~}U#8byi8o8IU#yT2wbh!syD zHZqD%P!uPhhe_gsIGN0aIm3_G^_=OW3~=jat$Bqr}o zwed>-54oBfA#OmM02NtJp6DFHjJY6*fR0~N$B@KakW2uFN)*+pK@xL8a=*~E{5GU9 z7lgjE^FPor8XehvNMm+VAKmi4upORROsoYr4~2-?AT&zue)b<_;+x3PLCl{R(swyU z`8f#`M=pQuJK#)Skp2=llNY2K z(32;I$O}>$17`w*@ZRH+7i4u7oXHA8`&rC48s!9`5TEA3U1)!T$`_i*mloLHVUcJD zp#WcJn{OeAL8236`~Lrp!}4JBZfavXG$C4|6Xe4JqCg)dWk!QAqd}O_Ak1hGW;6&h z8iW}Q!b~R%!vfF$u!G|#Wf!27Qc5YMlu}A5rBp=y0R^AlXy0glYybcN07*qoM6N<$ Ef|C;DQ~&?~ literal 0 HcmV?d00001 diff --git a/codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-skills.py b/codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-skills.py new file mode 100755 index 0000000000..0977c296ab --- /dev/null +++ b/codex-rs/core/src/skills/assets/samples/skill-installer/scripts/list-skills.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""List skills from a GitHub repo path.""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +import urllib.error + +from github_utils import github_api_contents_url, github_request + +DEFAULT_REPO = "openai/skills" +DEFAULT_PATH = "skills/.curated" +DEFAULT_REF = "main" + + +class ListError(Exception): + pass + + +class Args(argparse.Namespace): + repo: str + path: str + ref: str + format: str + + +def _request(url: str) -> bytes: + return github_request(url, "codex-skill-list") + + +def _codex_home() -> str: + return os.environ.get("CODEX_HOME", os.path.expanduser("~/.codex")) + + +def _installed_skills() -> set[str]: + root = os.path.join(_codex_home(), "skills") + if not os.path.isdir(root): + return set() + entries = set() + for name in os.listdir(root): + path = os.path.join(root, name) + if os.path.isdir(path): + entries.add(name) + return entries + + +def _list_skills(repo: str, path: str, ref: str) -> list[str]: + api_url = github_api_contents_url(repo, path, ref) + try: + payload = _request(api_url) + except urllib.error.HTTPError as exc: + if exc.code == 404: + raise ListError( + "Skills path not found: " + f"https://github.com/{repo}/tree/{ref}/{path}" + ) from exc + raise ListError(f"Failed to fetch skills: HTTP {exc.code}") from exc + data = json.loads(payload.decode("utf-8")) + if not isinstance(data, list): + raise ListError("Unexpected skills listing response.") + skills = [item["name"] for item in data if item.get("type") == "dir"] + return sorted(skills) + + +def _parse_args(argv: list[str]) -> Args: + parser = argparse.ArgumentParser(description="List skills.") + parser.add_argument("--repo", default=DEFAULT_REPO) + parser.add_argument( + "--path", + default=DEFAULT_PATH, + help="Repo path to list (default: skills/.curated)", + ) + parser.add_argument("--ref", default=DEFAULT_REF) + parser.add_argument( + "--format", + choices=["text", "json"], + default="text", + help="Output format", + ) + return parser.parse_args(argv, namespace=Args()) + + +def main(argv: list[str]) -> int: + args = _parse_args(argv) + try: + skills = _list_skills(args.repo, args.path, args.ref) + installed = _installed_skills() + if args.format == "json": + payload = [ + {"name": name, "installed": name in installed} for name in skills + ] + print(json.dumps(payload)) + else: + for idx, name in enumerate(skills, start=1): + suffix = " (already installed)" if name in installed else "" + print(f"{idx}. {name}{suffix}") + return 0 + except ListError as exc: + print(f"Error: {exc}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) From ae4eeff440197860618ad0ec4a27f205e080a21d Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sat, 31 Jan 2026 22:08:29 -0700 Subject: [PATCH 053/139] fix(config) config schema newline (#10323) ## Summary Looks like we may have introduced a formatting issue in recent PRs. ## Testing - [x] ran `just write-config-schema` --- codex-rs/core/config.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index c487659681..2315f75860 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1569,4 +1569,4 @@ }, "title": "ConfigToml", "type": "object" -} +} \ No newline at end of file From 3dd9a37e0bb7af065eed668c2c6a4c96cec85320 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Sat, 31 Jan 2026 23:20:27 -0800 Subject: [PATCH 054/139] Improve plan mode interaction rules (#10329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replace the “Hard interaction rule” with a clearer “Response constraints” section that enumerates the allowed exceptions for Plan Mode replies. - Remove the stray Phase 1 exception line about simple questions. - Update plan content requirements to ask for a brief summary section and generalize API/type wording. --- .../core/templates/collaboration_mode/plan.md | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/codex-rs/core/templates/collaboration_mode/plan.md b/codex-rs/core/templates/collaboration_mode/plan.md index 3b69f34fa1..adde92745a 100644 --- a/codex-rs/core/templates/collaboration_mode/plan.md +++ b/codex-rs/core/templates/collaboration_mode/plan.md @@ -42,10 +42,10 @@ When in doubt: if the action would reasonably be described as "doing the work" r Begin by grounding yourself in the actual environment. Eliminate unknowns in the prompt by discovering facts, not by asking the user. Resolve all questions that can be answered through exploration or inspection. Identify missing or ambiguous details only if they cannot be derived from the environment. Silent exploration between turns is allowed and encouraged. -Exception: simple, self-contained questions that does not need that. - Before asking the user any question, perform at least one targeted non-mutating exploration pass (for example: search relevant files, inspect likely entrypoints/configs, confirm current implementation shape), unless no local environment/repo is available. +Exception: you may ask clarifying questions about the user's prompt before exploring, ONLY if there are obvious ambiguities or contradictions in the prompt itself. However, if ambiguity might be resolved by exploring, always prefer exploring first. + Do not ask questions that can be answered from the repo or system (for example, "where is this struct?" or "which UI component should we use?" when exploration can make it clear). Only ask once you have exhausted reasonable non-mutating exploration. ## PHASE 2 — Intent chat (what they actually want) @@ -57,21 +57,13 @@ Do not ask questions that can be answered from the repo or system (for example, * Once intent is stable, keep asking until the spec is decision complete: approach, interfaces (APIs/schemas/I/O), data flow, edge cases/failure modes, testing + acceptance criteria, rollout/monitoring, and any migrations/compat constraints. -## Hard interaction rule (critical) +## Asking questions -Every assistant turn MUST be exactly one of: -A) a `request_user_input` tool call (questions/options only), OR -B) the final output: a titled, plan-only document, OR -C) Direct response to a simple, self-contained question (no planning, no implementation). +Critical rules: -Rules: - -* No questions in free text (only via `request_user_input`). -* Never mix a `request_user_input` call with plan content. -* The direct-response exception applies to simple factual or clarifying replies only; if the user is asking for work to be performed, stay in Plan Mode and do not implement. -* Internal tool/repo exploration is allowed privately before A or B. - -## Ask a lot, but never ask trivia +* Strongly prefer using the `request_user_input` tool to ask any questions. +* Offer only meaningful multiple‑choice options; don’t include filler choices that are obviously wrong or irrelevant. +* In rare cases where an unavoidable, important question can’t be expressed with reasonable multiple‑choice options (due to extreme ambiguity), you may ask it directly without the tool. You SHOULD ask many questions, but each question must: @@ -118,8 +110,8 @@ plan content plan content should be human and agent digestible. The final plan must be plan-only and include: * A clear title -* tldr section. don't necessary call it tldr. -* Important changes or additions of signatures, structs, types. +* A brief summary section +* Important changes or additions to public APIs/interfaces/types * Test cases and scenarios * Explicit assumptions and defaults chosen where needed From d3514bbdd221e1c32b381392a11600622d7d5e4f Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Sun, 1 Feb 2026 12:53:47 -0800 Subject: [PATCH 055/139] Bump thread updated_at on unarchive to refresh sidebar ordering (#10280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Touch restored rollout files on `thread/unarchive` so `updatedAt` reflects the unarchive time. - Add a regression test to ensure unarchiving bumps `updated_at` from an old mtime. ## Notes This fixes the UX issue where unarchived old threads don’t reappear near the top of recent threads. --- .../app-server/src/codex_message_processor.rs | 25 +++++++++++++++++++ .../tests/suite/v2/thread_unarchive.rs | 22 +++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 76b2393e8a..d044786bdf 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -202,6 +202,8 @@ use codex_utils_json_to_toml::json_to_toml; use std::collections::HashMap; use std::collections::HashSet; use std::ffi::OsStr; +use std::fs::FileTimes; +use std::fs::OpenOptions; use std::io::Error as IoError; use std::path::Path; use std::path::PathBuf; @@ -209,6 +211,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; +use std::time::SystemTime; use tokio::sync::Mutex; use tokio::sync::broadcast; use tokio::sync::oneshot; @@ -1978,6 +1981,28 @@ impl CodexMessageProcessor { message: format!("failed to unarchive thread: {err}"), data: None, })?; + tokio::task::spawn_blocking({ + let restored_path = restored_path.clone(); + move || -> std::io::Result<()> { + let times = FileTimes::new().set_modified(SystemTime::now()); + OpenOptions::new() + .append(true) + .open(&restored_path)? + .set_times(times)?; + Ok(()) + } + }) + .await + .map_err(|err| JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, + message: format!("failed to update unarchived thread timestamp: {err}"), + data: None, + })? + .map_err(|err| JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, + message: format!("failed to update unarchived thread timestamp: {err}"), + data: None, + })?; if let Some(ctx) = state_db_ctx { let _ = ctx .mark_unarchived(thread_id, restored_path.as_path()) diff --git a/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs b/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs index c56b39e33b..dada1cbf20 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs @@ -11,7 +11,11 @@ use codex_app_server_protocol::ThreadUnarchiveParams; use codex_app_server_protocol::ThreadUnarchiveResponse; use codex_core::find_archived_thread_path_by_id_str; use codex_core::find_thread_path_by_id_str; +use std::fs::FileTimes; +use std::fs::OpenOptions; use std::path::Path; +use std::time::Duration; +use std::time::SystemTime; use tempfile::TempDir; use tokio::time::timeout; @@ -62,6 +66,16 @@ async fn thread_unarchive_moves_rollout_back_into_sessions_directory() -> Result archived_path.exists(), "expected {archived_path_display} to exist" ); + let old_time = SystemTime::UNIX_EPOCH + Duration::from_secs(1); + let old_timestamp = old_time + .duration_since(SystemTime::UNIX_EPOCH) + .expect("old timestamp") + .as_secs() as i64; + let times = FileTimes::new().set_modified(old_time); + OpenOptions::new() + .append(true) + .open(&archived_path)? + .set_times(times)?; let unarchive_id = mcp .send_thread_unarchive_request(ThreadUnarchiveParams { @@ -73,7 +87,13 @@ async fn thread_unarchive_moves_rollout_back_into_sessions_directory() -> Result mcp.read_stream_until_response_message(RequestId::Integer(unarchive_id)), ) .await??; - let _: ThreadUnarchiveResponse = to_response::(unarchive_resp)?; + let ThreadUnarchiveResponse { + thread: unarchived_thread, + } = to_response::(unarchive_resp)?; + assert!( + unarchived_thread.updated_at > old_timestamp, + "expected updated_at to be bumped on unarchive" + ); let rollout_path_display = rollout_path.display(); assert!( From 5fb46187b2c53b861edf17dacd95e8dfc1866677 Mon Sep 17 00:00:00 2001 From: Gav Verma Date: Sun, 1 Feb 2026 18:17:32 -0800 Subject: [PATCH 056/139] fix: System skills marker includes nested folders recursively (#10350) Updated system skills bundled with Codex were not correctly replacing the user's skills in their .system folder. - Fix `.codex-system-skills.marker` not updating by hashing embedded system skills recursively (nested dirs + file contents), so updates trigger a reinstall. - Added a build Cargo hook to rerun if there are changes in `src/skills/assets/samples/*`, ensuring embedded skill updates rebuild correctly under caching. - Add a small unit test to ensure nested entries are included in the fingerprint. --- codex-rs/core/Cargo.toml | 1 + codex-rs/core/build.rs | 27 +++++++++++++ codex-rs/core/src/skills/system.rs | 61 ++++++++++++++++++++++-------- 3 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 codex-rs/core/build.rs diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 66ed20a435..a857da8b3a 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -3,6 +3,7 @@ edition.workspace = true license.workspace = true name = "codex-core" version.workspace = true +build = "build.rs" [lib] doctest = false diff --git a/codex-rs/core/build.rs b/codex-rs/core/build.rs new file mode 100644 index 0000000000..587415a3fd --- /dev/null +++ b/codex-rs/core/build.rs @@ -0,0 +1,27 @@ +use std::fs; +use std::path::Path; + +fn main() { + let samples_dir = Path::new("src/skills/assets/samples"); + if !samples_dir.exists() { + return; + } + + println!("cargo:rerun-if-changed={}", samples_dir.display()); + visit_dir(samples_dir); +} + +fn visit_dir(dir: &Path) { + let entries = match fs::read_dir(dir) { + Ok(entries) => entries, + Err(_) => return, + }; + + for entry in entries.flatten() { + let path = entry.path(); + println!("cargo:rerun-if-changed={}", path.display()); + if path.is_dir() { + visit_dir(&path); + } + } +} diff --git a/codex-rs/core/src/skills/system.rs b/codex-rs/core/src/skills/system.rs index cfa20045a5..cf8404096d 100644 --- a/codex-rs/core/src/skills/system.rs +++ b/codex-rs/core/src/skills/system.rs @@ -86,21 +86,8 @@ fn read_marker(path: &AbsolutePathBuf) -> Result { } fn embedded_system_skills_fingerprint() -> String { - let mut items: Vec<(String, Option)> = SYSTEM_SKILLS_DIR - .entries() - .iter() - .map(|entry| match entry { - include_dir::DirEntry::Dir(dir) => (dir.path().to_string_lossy().to_string(), None), - include_dir::DirEntry::File(file) => { - let mut file_hasher = DefaultHasher::new(); - file.contents().hash(&mut file_hasher); - ( - file.path().to_string_lossy().to_string(), - Some(file_hasher.finish()), - ) - } - }) - .collect(); + let mut items = Vec::new(); + collect_fingerprint_items(&SYSTEM_SKILLS_DIR, &mut items); items.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); let mut hasher = DefaultHasher::new(); @@ -112,6 +99,25 @@ fn embedded_system_skills_fingerprint() -> String { format!("{:x}", hasher.finish()) } +fn collect_fingerprint_items(dir: &Dir<'_>, items: &mut Vec<(String, Option)>) { + for entry in dir.entries() { + match entry { + include_dir::DirEntry::Dir(subdir) => { + items.push((subdir.path().to_string_lossy().to_string(), None)); + collect_fingerprint_items(subdir, items); + } + include_dir::DirEntry::File(file) => { + let mut file_hasher = DefaultHasher::new(); + file.contents().hash(&mut file_hasher); + items.push(( + file.path().to_string_lossy().to_string(), + Some(file_hasher.finish()), + )); + } + } + } +} + /// Writes the embedded `include_dir::Dir` to disk under `dest`. /// /// Preserves the embedded directory structure. @@ -163,3 +169,28 @@ impl SystemSkillsError { Self::Io { action, source } } } + +#[cfg(test)] +mod tests { + use super::SYSTEM_SKILLS_DIR; + use super::collect_fingerprint_items; + + #[test] + fn fingerprint_traverses_nested_entries() { + let mut items = Vec::new(); + collect_fingerprint_items(&SYSTEM_SKILLS_DIR, &mut items); + let mut paths: Vec = items.into_iter().map(|(path, _)| path).collect(); + paths.sort_unstable(); + + assert!( + paths + .binary_search_by(|probe| probe.as_str().cmp("skill-creator/SKILL.md")) + .is_ok() + ); + assert!( + paths + .binary_search_by(|probe| probe.as_str().cmp("skill-creator/scripts/init_skill.py")) + .is_ok() + ); + } +} From 8b95d3e082376f4cb23e92641705a22afb28a9da Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sun, 1 Feb 2026 18:26:15 -0800 Subject: [PATCH 057/139] fix(rules) Limit rules listed in conversation (#10351) ## Summary We should probably warn users that they have a million rules, and help clean them up. But for now, we should handle this unbounded case. Limit rules listed in conversations, with shortest / broadest rules first. ## Testing - [x] Updated unit tests --- codex-rs/core/src/codex.rs | 4 +- codex-rs/protocol/src/models.rs | 124 ++++++++++++++++++++++++++------ 2 files changed, 106 insertions(+), 22 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 29b02c006a..4acc1ef4e9 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -51,6 +51,7 @@ use codex_protocol::items::PlanItem; use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; use codex_protocol::models::BaseInstructions; +use codex_protocol::models::format_allow_prefixes; use codex_protocol::openai_models::ModelInfo; use codex_protocol::protocol::FileChange; use codex_protocol::protocol::HasLegacyEvent; @@ -212,7 +213,6 @@ use codex_protocol::models::ContentItem; use codex_protocol::models::DeveloperInstructions; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; -use codex_protocol::models::render_command_prefix_list; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::InitialHistory; use codex_protocol::user_input::UserInput; @@ -1522,7 +1522,7 @@ impl Session { sub_id: &str, amendment: &ExecPolicyAmendment, ) { - let Some(prefixes) = render_command_prefix_list([amendment.command.as_slice()]) else { + let Some(prefixes) = format_allow_prefixes(vec![amendment.command.clone()]) else { warn!("execpolicy amendment for {sub_id} had no command prefix"); return; }; diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 8e67fc40fc..9dffb02565 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -233,10 +233,13 @@ impl DeveloperInstructions { if !request_rule_enabled { APPROVAL_POLICY_ON_REQUEST.to_string() } else { - let command_prefixes = format_allow_prefixes(exec_policy); + let command_prefixes = + format_allow_prefixes(exec_policy.get_allowed_prefixes()); match command_prefixes { Some(prefixes) => { - format!("{APPROVAL_POLICY_ON_REQUEST_RULE}\n{prefixes}") + format!( + "{APPROVAL_POLICY_ON_REQUEST_RULE}\nApproved command prefixes:\n{prefixes}" + ) } None => APPROVAL_POLICY_ON_REQUEST_RULE.to_string(), } @@ -371,20 +374,51 @@ impl DeveloperInstructions { } } -pub fn render_command_prefix_list(prefixes: I) -> Option -where - I: IntoIterator, - P: AsRef<[String]>, -{ - let lines = prefixes - .into_iter() - .map(|prefix| format!("- {}", render_command_prefix(prefix.as_ref()))) - .collect::>(); - if lines.is_empty() { - return None; +const MAX_RENDERED_PREFIXES: usize = 100; +const MAX_ALLOW_PREFIX_TEXT_BYTES: usize = 5000; +const TRUNCATED_MARKER: &str = "...\n[Some commands were truncated]"; + +pub fn format_allow_prefixes(prefixes: Vec>) -> Option { + let mut truncated = false; + if prefixes.len() > MAX_RENDERED_PREFIXES { + truncated = true; } - Some(lines.join("\n")) + let mut prefixes = prefixes; + prefixes.sort_by(|a, b| { + a.len() + .cmp(&b.len()) + .then_with(|| prefix_combined_str_len(a).cmp(&prefix_combined_str_len(b))) + .then_with(|| a.cmp(b)) + }); + + let full_text = prefixes + .into_iter() + .take(MAX_RENDERED_PREFIXES) + .map(|prefix| format!("- {}", render_command_prefix(&prefix))) + .collect::>() + .join("\n"); + + // truncate to last UTF8 char + let mut output = full_text; + let byte_idx = output + .char_indices() + .nth(MAX_ALLOW_PREFIX_TEXT_BYTES) + .map(|(i, _)| i); + if let Some(byte_idx) = byte_idx { + truncated = true; + output = output[..byte_idx].to_string(); + } + + if truncated { + Some(format!("{output}{TRUNCATED_MARKER}")) + } else { + Some(output) + } +} + +fn prefix_combined_str_len(prefix: &[String]) -> usize { + prefix.iter().map(String::len).sum() } fn render_command_prefix(prefix: &[String]) -> String { @@ -396,12 +430,6 @@ fn render_command_prefix(prefix: &[String]) -> String { format!("[{tokens}]") } -fn format_allow_prefixes(exec_policy: &Policy) -> Option { - let prefixes = exec_policy.get_allowed_prefixes(); - let lines = render_command_prefix_list(prefixes)?; - Some(format!("Approved command prefixes:\n{lines}")) -} - impl From for ResponseItem { fn from(di: DeveloperInstructions) -> Self { ResponseItem::Message { @@ -1000,6 +1028,62 @@ mod tests { assert!(text.contains(r#"["git", "pull"]"#)); } + #[test] + fn render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical() { + let prefixes = vec![ + vec!["b".to_string(), "zz".to_string()], + vec!["aa".to_string()], + vec!["b".to_string()], + vec!["a".to_string(), "b".to_string(), "c".to_string()], + vec!["a".to_string()], + vec!["b".to_string(), "a".to_string()], + ]; + + let output = format_allow_prefixes(prefixes).expect("rendered list"); + assert_eq!( + output, + r#"- ["a"] +- ["b"] +- ["aa"] +- ["b", "a"] +- ["b", "zz"] +- ["a", "b", "c"]"# + .to_string(), + ); + } + + #[test] + fn render_command_prefix_list_limits_output_to_max_prefixes() { + let prefixes = (0..(MAX_RENDERED_PREFIXES + 5)) + .map(|i| vec![format!("{i:03}")]) + .collect::>(); + + let output = format_allow_prefixes(prefixes).expect("rendered list"); + assert_eq!(output.ends_with(TRUNCATED_MARKER), true); + eprintln!("output: {output}"); + assert_eq!(output.lines().count(), MAX_RENDERED_PREFIXES + 1); + } + + #[test] + fn format_allow_prefixes_limits_output() { + let mut exec_policy = Policy::empty(); + for i in 0..200 { + exec_policy + .add_prefix_rule( + &[format!("tool-{i:03}"), "x".repeat(500)], + codex_execpolicy::Decision::Allow, + ) + .expect("add rule"); + } + + let output = + format_allow_prefixes(exec_policy.get_allowed_prefixes()).expect("formatted prefixes"); + assert!( + output.len() <= MAX_ALLOW_PREFIX_TEXT_BYTES + TRUNCATED_MARKER.len(), + "output length exceeds expected limit: {output}", + ); + } + #[test] fn serializes_success_as_plain_string() -> Result<()> { let item = ResponseInputItem::FunctionCallOutput { From 03fcd12e77fedf4fa327af27e2e476e1ebc5f651 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Sun, 1 Feb 2026 18:51:26 -0800 Subject: [PATCH 058/139] Do not append items on override turn context (#10354) --- codex-rs/core/src/codex.rs | 77 ++------ codex-rs/core/src/compact.rs | 2 +- codex-rs/core/src/compact_remote.rs | 2 +- codex-rs/core/src/stream_events_utils.rs | 2 +- codex-rs/core/src/tasks/user_shell.rs | 2 +- codex-rs/core/src/tools/handlers/plan.rs | 2 +- .../tests/suite/collaboration_instructions.rs | 178 ++++++++++++++++-- codex-rs/core/tests/suite/override_updates.rs | 22 +-- .../core/tests/suite/permissions_messages.rs | 6 +- codex-rs/core/tests/suite/personality.rs | 91 +++++++++ codex-rs/core/tests/suite/prompt_caching.rs | 5 +- 11 files changed, 288 insertions(+), 101 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 4acc1ef4e9..ba5ee02d69 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -490,7 +490,7 @@ pub(crate) struct TurnContext { pub(crate) developer_instructions: Option, pub(crate) compact_prompt: Option, pub(crate) user_instructions: Option, - pub(crate) collaboration_mode_kind: ModeKind, + pub(crate) collaboration_mode: CollaborationMode, pub(crate) personality: Option, pub(crate) approval_policy: AskForApproval, pub(crate) sandbox_policy: SandboxPolicy, @@ -692,7 +692,7 @@ impl Session { developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), user_instructions: session_configuration.user_instructions.clone(), - collaboration_mode_kind: session_configuration.collaboration_mode.mode, + collaboration_mode: session_configuration.collaboration_mode.clone(), personality: session_configuration.personality, approval_policy: session_configuration.approval_policy.value(), sandbox_policy: session_configuration.sandbox_policy.get().clone(), @@ -1356,16 +1356,14 @@ impl Session { fn build_collaboration_mode_update_item( &self, - previous_collaboration_mode: &CollaborationMode, - next_collaboration_mode: Option<&CollaborationMode>, + previous: Option<&Arc>, + next: &TurnContext, ) -> Option { - if let Some(next_mode) = next_collaboration_mode { - if previous_collaboration_mode == next_mode { - return None; - } + let prev = previous?; + if prev.collaboration_mode != next.collaboration_mode { // If the next mode has empty developer instructions, this returns None and we emit no // update, so prior collaboration instructions remain in the prompt history. - Some(DeveloperInstructions::from_collaboration_mode(next_mode)?.into()) + Some(DeveloperInstructions::from_collaboration_mode(&next.collaboration_mode)?.into()) } else { None } @@ -1375,8 +1373,6 @@ impl Session { &self, previous_context: Option<&Arc>, current_context: &TurnContext, - previous_collaboration_mode: &CollaborationMode, - next_collaboration_mode: Option<&CollaborationMode>, ) -> Vec { let mut update_items = Vec::new(); if let Some(env_item) = @@ -1389,10 +1385,9 @@ impl Session { { update_items.push(permissions_item); } - if let Some(collaboration_mode_item) = self.build_collaboration_mode_update_item( - previous_collaboration_mode, - next_collaboration_mode, - ) { + if let Some(collaboration_mode_item) = + self.build_collaboration_mode_update_item(previous_context, current_context) + { update_items.push(collaboration_mode_item); } if let Some(personality_item) = @@ -2573,18 +2568,6 @@ mod handlers { sub_id: String, updates: SessionSettingsUpdate, ) { - let previous_context = sess - .new_default_turn_with_sub_id(sess.next_internal_sub_id()) - .await; - let previous_collaboration_mode = sess - .state - .lock() - .await - .session_configuration - .collaboration_mode - .clone(); - let next_collaboration_mode = updates.collaboration_mode.clone(); - if let Err(err) = sess.update_settings(updates).await { sess.send_event_raw(Event { id: sub_id, @@ -2594,24 +2577,6 @@ mod handlers { }), }) .await; - return; - } - - let initial_context_seeded = sess.state.lock().await.initial_context_seeded; - if !initial_context_seeded { - return; - } - - let current_context = sess.new_default_turn_with_sub_id(sub_id).await; - let update_items = sess.build_settings_update_items( - Some(&previous_context), - ¤t_context, - &previous_collaboration_mode, - next_collaboration_mode.as_ref(), - ); - if !update_items.is_empty() { - sess.record_conversation_items(¤t_context, &update_items) - .await; } } @@ -2671,14 +2636,6 @@ mod handlers { _ => unreachable!(), }; - let previous_collaboration_mode = sess - .state - .lock() - .await - .session_configuration - .collaboration_mode - .clone(); - let next_collaboration_mode = updates.collaboration_mode.clone(); let Ok(current_context) = sess.new_turn_with_sub_id(sub_id, updates).await else { // new_turn_with_sub_id already emits the error event. return; @@ -2691,12 +2648,8 @@ mod handlers { // Attempt to inject input into current task if let Err(items) = sess.inject_input(items).await { sess.seed_initial_context_if_needed(¤t_context).await; - let update_items = sess.build_settings_update_items( - previous_context.as_ref(), - ¤t_context, - &previous_collaboration_mode, - next_collaboration_mode.as_ref(), - ); + let update_items = + sess.build_settings_update_items(previous_context.as_ref(), ¤t_context); if !update_items.is_empty() { sess.record_conversation_items(¤t_context, &update_items) .await; @@ -3211,7 +3164,7 @@ async fn spawn_review_thread( developer_instructions: None, user_instructions: None, compact_prompt: parent_turn_context.compact_prompt.clone(), - collaboration_mode_kind: parent_turn_context.collaboration_mode_kind, + collaboration_mode: parent_turn_context.collaboration_mode.clone(), personality: parent_turn_context.personality, approval_policy: parent_turn_context.approval_policy, sandbox_policy: parent_turn_context.sandbox_policy.clone(), @@ -3326,7 +3279,7 @@ pub(crate) async fn run_turn( let total_usage_tokens = sess.get_total_token_usage().await; let event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), - collaboration_mode_kind: turn_context.collaboration_mode_kind, + collaboration_mode_kind: turn_context.collaboration_mode.mode, }); sess.send_event(&turn_context, event).await; if total_usage_tokens >= auto_compact_limit { @@ -4239,7 +4192,7 @@ async fn try_run_sampling_request( let mut last_agent_message: Option = None; let mut active_item: Option = None; let mut should_emit_turn_diff = false; - let plan_mode = turn_context.collaboration_mode_kind == ModeKind::Plan; + let plan_mode = turn_context.collaboration_mode.mode == ModeKind::Plan; let mut plan_mode_state = plan_mode.then(|| PlanModeStreamState::new(&turn_context.sub_id)); let receiving_span = trace_span!("receiving_stream"); let outcome: CodexResult = loop { diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index af9aacc769..7e7445a6f6 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -61,7 +61,7 @@ pub(crate) async fn run_compact_task( ) { let start_event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), - collaboration_mode_kind: turn_context.collaboration_mode_kind, + collaboration_mode_kind: turn_context.collaboration_mode.mode, }); sess.send_event(&turn_context, start_event).await; run_compact_task_inner(sess.clone(), turn_context, input).await; diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index b45a2769dc..9f7a8dfea3 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -22,7 +22,7 @@ pub(crate) async fn run_inline_remote_auto_compact_task( pub(crate) async fn run_remote_compact_task(sess: Arc, turn_context: Arc) { let start_event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), - collaboration_mode_kind: turn_context.collaboration_mode_kind, + collaboration_mode_kind: turn_context.collaboration_mode.mode, }); sess.send_event(&turn_context, start_event).await; diff --git a/codex-rs/core/src/stream_events_utils.rs b/codex-rs/core/src/stream_events_utils.rs index 91f7efdb01..02d9822510 100644 --- a/codex-rs/core/src/stream_events_utils.rs +++ b/codex-rs/core/src/stream_events_utils.rs @@ -48,7 +48,7 @@ pub(crate) async fn handle_output_item_done( previously_active_item: Option, ) -> Result { let mut output = OutputItemResult::default(); - let plan_mode = ctx.turn_context.collaboration_mode_kind == ModeKind::Plan; + let plan_mode = ctx.turn_context.collaboration_mode.mode == ModeKind::Plan; match ToolRouter::build_tool_call(ctx.sess.as_ref(), item.clone()).await { // The model emitted a tool call; log it, persist the item immediately, and queue the tool execution. diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index 177d122554..fe3688e198 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -67,7 +67,7 @@ impl SessionTask for UserShellCommandTask { let event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), - collaboration_mode_kind: turn_context.collaboration_mode_kind, + collaboration_mode_kind: turn_context.collaboration_mode.mode, }); let session = session.clone_session(); session.send_event(turn_context.as_ref(), event).await; diff --git a/codex-rs/core/src/tools/handlers/plan.rs b/codex-rs/core/src/tools/handlers/plan.rs index 79e332a77e..88e0b0dc27 100644 --- a/codex-rs/core/src/tools/handlers/plan.rs +++ b/codex-rs/core/src/tools/handlers/plan.rs @@ -104,7 +104,7 @@ pub(crate) async fn handle_update_plan( arguments: String, _call_id: String, ) -> Result { - if turn_context.collaboration_mode_kind == ModeKind::Plan { + if turn_context.collaboration_mode.mode == ModeKind::Plan { return Err(FunctionCallError::RespondToModel( "update_plan is a TODO/checklist tool and is not allowed in Plan mode".to_string(), )); diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 7a311b4bf1..9dcecdf383 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -22,9 +22,12 @@ fn sse_completed(id: &str) -> String { sse(vec![ev_response_created(id), ev_completed(id)]) } -fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMode { +fn collab_mode_with_mode_and_instructions( + mode: ModeKind, + instructions: Option<&str>, +) -> CollaborationMode { CollaborationMode { - mode: ModeKind::Custom, + mode, settings: Settings { model: "gpt-5.1".to_string(), reasoning_effort: None, @@ -33,6 +36,10 @@ fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMod } } +fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMode { + collab_mode_with_mode_and_instructions(ModeKind::Custom, instructions) +} + fn developer_texts(input: &[Value]) -> Vec { input .iter() @@ -171,7 +178,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_then_user_turn_uses_updated_collaboration_instructions() -> Result<()> { +async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -196,20 +203,12 @@ async fn override_then_user_turn_uses_updated_collaboration_instructions() -> Re .await?; test.codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello".into(), text_elements: Vec::new(), }], - cwd: test.config.cwd.clone(), - approval_policy: test.config.approval_policy.value(), - sandbox_policy: test.config.sandbox_policy.get().clone(), - model: test.session_configured.model.clone(), - effort: None, - summary: test.config.model_reasoning_summary, - collaboration_mode: None, final_output_json_schema: None, - personality: None, }) .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -272,7 +271,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu let dev_texts = developer_texts(&input); let base_text = collab_xml(base_text); let turn_text = collab_xml(turn_text); - assert_eq!(count_exact(&dev_texts, &base_text), 1); + assert_eq!(count_exact(&dev_texts, &base_text), 0); assert_eq!(count_exact(&dev_texts, &turn_text), 1); Ok(()) @@ -419,6 +418,159 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn collaboration_mode_update_emits_new_instruction_message_when_mode_changes() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; + let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + + let test = test_codex().build(&server).await?; + let code_text = "code mode instructions"; + let plan_text = "plan mode instructions"; + + test.codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: None, + effort: None, + summary: None, + collaboration_mode: Some(collab_mode_with_mode_and_instructions( + ModeKind::Code, + Some(code_text), + )), + personality: None, + }) + .await?; + + test.codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "hello 1".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + test.codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: None, + effort: None, + summary: None, + collaboration_mode: Some(collab_mode_with_mode_and_instructions( + ModeKind::Plan, + Some(plan_text), + )), + personality: None, + }) + .await?; + + test.codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "hello 2".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + let input = req2.single_request().input(); + let dev_texts = developer_texts(&input); + let code_text = collab_xml(code_text); + let plan_text = collab_xml(plan_text); + assert_eq!(count_exact(&dev_texts, &code_text), 1); + assert_eq!(count_exact(&dev_texts, &plan_text), 1); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; + let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + + let test = test_codex().build(&server).await?; + let collab_text = "mode-stable instructions"; + + test.codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: None, + effort: None, + summary: None, + collaboration_mode: Some(collab_mode_with_mode_and_instructions( + ModeKind::Code, + Some(collab_text), + )), + personality: None, + }) + .await?; + + test.codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "hello 1".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + test.codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: None, + effort: None, + summary: None, + collaboration_mode: Some(collab_mode_with_mode_and_instructions( + ModeKind::Code, + Some(collab_text), + )), + personality: None, + }) + .await?; + + test.codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "hello 2".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + let input = req2.single_request().input(); + let dev_texts = developer_texts(&input); + let collab_text = collab_xml(collab_text); + assert_eq!(count_exact(&dev_texts, &collab_text), 1); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn resume_replays_collaboration_instructions() -> Result<()> { skip_if_no_network!(Ok(())); diff --git a/codex-rs/core/tests/suite/override_updates.rs b/codex-rs/core/tests/suite/override_updates.rs index 14469c9643..897bb665d8 100644 --- a/codex-rs/core/tests/suite/override_updates.rs +++ b/codex-rs/core/tests/suite/override_updates.rs @@ -18,7 +18,6 @@ use core_test_support::skip_if_no_network; use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; use pretty_assertions::assert_eq; -use std::collections::HashSet; use std::path::Path; use std::time::Duration; use tempfile::TempDir; @@ -104,7 +103,7 @@ fn rollout_environment_texts(text: &str) -> Vec { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_records_permissions_update() -> Result<()> { +async fn override_turn_context_without_user_turn_does_not_record_permissions_update() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -138,19 +137,15 @@ async fn override_turn_context_records_permissions_update() -> Result<()> { .filter(|text| text.contains("`approval_policy`")) .collect(); assert!( - approval_texts - .iter() - .any(|text| text.contains("`approval_policy` is `never`")), - "expected updated approval policy instructions in rollout" + approval_texts.is_empty(), + "did not expect permissions updates before a new user turn: {approval_texts:?}" ); - let unique: HashSet<&String> = approval_texts.iter().copied().collect(); - assert_eq!(unique.len(), 2); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_records_environment_update() -> Result<()> { +async fn override_turn_context_without_user_turn_does_not_record_environment_update() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -177,17 +172,16 @@ async fn override_turn_context_records_environment_update() -> Result<()> { let rollout_path = test.codex.rollout_path().expect("rollout path"); let rollout_text = read_rollout_text(&rollout_path).await?; let env_texts = rollout_environment_texts(&rollout_text); - let new_cwd_text = new_cwd.path().display().to_string(); assert!( - env_texts.iter().any(|text| text.contains(&new_cwd_text)), - "expected environment update with new cwd in rollout" + env_texts.is_empty(), + "did not expect environment updates before a new user turn: {env_texts:?}" ); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_records_collaboration_update() -> Result<()> { +async fn override_turn_context_without_user_turn_does_not_record_collaboration_update() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -220,7 +214,7 @@ async fn override_turn_context_records_collaboration_update() -> Result<()> { .iter() .filter(|text| text.as_str() == collab_text.as_str()) .count(); - assert_eq!(collab_count, 1); + assert_eq!(collab_count, 0); Ok(()) } diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index b500689ef6..c54203b4f1 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -136,7 +136,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> { let permissions_2 = permissions_texts(input2); assert_eq!(permissions_1.len(), 1); - assert_eq!(permissions_2.len(), 3); + assert_eq!(permissions_2.len(), 2); let unique = permissions_2.into_iter().collect::>(); assert_eq!(unique.len(), 2); @@ -267,7 +267,7 @@ async fn resume_replays_permissions_messages() -> Result<()> { let body3 = req3.single_request().body_json(); let input = body3["input"].as_array().expect("input array"); let permissions = permissions_texts(input); - assert_eq!(permissions.len(), 4); + assert_eq!(permissions.len(), 3); let unique = permissions.into_iter().collect::>(); assert_eq!(unique.len(), 2); @@ -337,7 +337,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { let body2 = req2.single_request().body_json(); let input2 = body2["input"].as_array().expect("input array"); let permissions_base = permissions_texts(input2); - assert_eq!(permissions_base.len(), 3); + assert_eq!(permissions_base.len(), 2); builder = builder.with_config(|config| { config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted); diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 2b00574d58..85cc25f4cd 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -272,6 +272,97 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn user_turn_personality_same_value_does_not_add_update_message() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let resp_mock = mount_sse_sequence( + &server, + vec![sse_completed("resp-1"), sse_completed("resp-2")], + ) + .await; + let mut builder = test_codex() + .with_model("exp-codex-personality") + .with_config(|config| { + config.features.disable(Feature::RemoteModels); + config.features.enable(Feature::Personality); + config.personality = Some(Personality::Pragmatic); + }); + let test = builder.build(&server).await?; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: test.config.approval_policy.value(), + sandbox_policy: SandboxPolicy::ReadOnly, + model: test.session_configured.model.clone(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + test.codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: None, + effort: None, + summary: None, + collaboration_mode: None, + personality: Some(Personality::Pragmatic), + }) + .await?; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: test.config.approval_policy.value(), + sandbox_policy: SandboxPolicy::ReadOnly, + model: test.session_configured.model.clone(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + let requests = resp_mock.requests(); + assert_eq!(requests.len(), 2, "expected two requests"); + let request = requests + .last() + .expect("expected second request after personality override"); + + let developer_texts = request.message_input_texts("developer"); + let personality_text = developer_texts + .iter() + .find(|text| text.contains("")); + assert!( + personality_text.is_none(), + "expected no personality preamble for unchanged personality, got {personality_text:?}" + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn instructions_uses_base_if_feature_disabled() -> anyhow::Result<()> { let codex_home = TempDir::new().expect("create temp dir"); diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 579a1856d4..6b06726eae 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -388,17 +388,14 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an }); let expected_permissions_msg = body1["input"][0].clone(); let body1_input = body1["input"].as_array().expect("input array"); - // After overriding the turn context, emit two updated permissions messages. + // After overriding the turn context, emit one updated permissions message. let expected_permissions_msg_2 = body2["input"][body1_input.len()].clone(); - let expected_permissions_msg_3 = body2["input"][body1_input.len() + 1].clone(); assert_ne!( expected_permissions_msg_2, expected_permissions_msg, "expected updated permissions message after override" ); - assert_eq!(expected_permissions_msg_2, expected_permissions_msg_3); let mut expected_body2 = body1_input.to_vec(); expected_body2.push(expected_permissions_msg_2); - expected_body2.push(expected_permissions_msg_3); expected_body2.push(expected_user_message_2); assert_eq!(body2["input"], serde_json::Value::Array(expected_body2)); From 6c22360bcbbcf97a75725a6cab1c5c7da2848085 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sun, 1 Feb 2026 20:30:38 -0800 Subject: [PATCH 059/139] fix(core) Deduplicate prefix_rules before appending (#10309) ## Summary We ideally shouldn't make it to this point in the first place, but if we do try to append a rule that already exists, we shouldn't append the same rule twice. ## Testing - [x] Added unit test for this case --- codex-rs/execpolicy/src/amend.rs | 39 ++++++++++++------------------ codex-rs/execpolicy/tests/basic.rs | 21 ++++++++++++++++ 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/codex-rs/execpolicy/src/amend.rs b/codex-rs/execpolicy/src/amend.rs index 9114d3a64f..9809a46fe2 100644 --- a/codex-rs/execpolicy/src/amend.rs +++ b/codex-rs/execpolicy/src/amend.rs @@ -105,35 +105,28 @@ fn append_locked_line(policy_path: &Path, line: &str) -> Result<(), AmendError> source, })?; - let len = file - .metadata() - .map_err(|source| AmendError::PolicyMetadata { + file.seek(SeekFrom::Start(0)) + .map_err(|source| AmendError::SeekPolicyFile { path: policy_path.to_path_buf(), source, - })? - .len(); + })?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|source| AmendError::ReadPolicyFile { + path: policy_path.to_path_buf(), + source, + })?; - // Ensure file ends in a newline before appending. - if len > 0 { - file.seek(SeekFrom::End(-1)) - .map_err(|source| AmendError::SeekPolicyFile { + if contents.lines().any(|existing| existing == line) { + return Ok(()); + } + + if !contents.is_empty() && !contents.ends_with('\n') { + file.write_all(b"\n") + .map_err(|source| AmendError::WritePolicyFile { path: policy_path.to_path_buf(), source, })?; - let mut last = [0; 1]; - file.read_exact(&mut last) - .map_err(|source| AmendError::ReadPolicyFile { - path: policy_path.to_path_buf(), - source, - })?; - - if last[0] != b'\n' { - file.write_all(b"\n") - .map_err(|source| AmendError::WritePolicyFile { - path: policy_path.to_path_buf(), - source, - })?; - } } file.write_all(format!("{line}\n").as_bytes()) diff --git a/codex-rs/execpolicy/tests/basic.rs b/codex-rs/execpolicy/tests/basic.rs index ed6cf3185e..040509f115 100644 --- a/codex-rs/execpolicy/tests/basic.rs +++ b/codex-rs/execpolicy/tests/basic.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::fs; use std::sync::Arc; use anyhow::Context; @@ -10,10 +11,12 @@ use codex_execpolicy::Policy; use codex_execpolicy::PolicyParser; use codex_execpolicy::RuleMatch; use codex_execpolicy::RuleRef; +use codex_execpolicy::blocking_append_allow_prefix_rule; use codex_execpolicy::rule::PatternToken; use codex_execpolicy::rule::PrefixPattern; use codex_execpolicy::rule::PrefixRule; use pretty_assertions::assert_eq; +use tempfile::tempdir; fn tokens(cmd: &[&str]) -> Vec { cmd.iter().map(std::string::ToString::to_string).collect() @@ -46,6 +49,24 @@ fn rule_snapshots(rules: &[RuleRef]) -> Vec { .collect() } +#[test] +fn append_allow_prefix_rule_dedupes_existing_rule() -> Result<()> { + let tmp = tempdir().context("create temp dir")?; + let policy_path = tmp.path().join("rules").join("default.rules"); + let prefix = tokens(&["python3"]); + + blocking_append_allow_prefix_rule(&policy_path, &prefix)?; + blocking_append_allow_prefix_rule(&policy_path, &prefix)?; + + let contents = fs::read_to_string(&policy_path).context("read policy")?; + assert_eq!( + contents, + r#"prefix_rule(pattern=["python3"], decision="allow") +"# + ); + Ok(()) +} + #[test] fn basic_match() -> Result<()> { let policy_src = r#" From a90ff831e7d7a049c5638cda6fa72f2abc0b62e6 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sun, 1 Feb 2026 22:54:12 -0800 Subject: [PATCH 060/139] chore(core) gpt-5.2-codex personality template (#10373) ## Summary Consolidate prompts ## Testing - [x] Existing tests pass --- .../gpt-5.2-codex_instructions_template.md | 93 ++++++++++++------- 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md b/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md index 530e825d5e..1af660d60a 100644 --- a/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md +++ b/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md @@ -2,56 +2,79 @@ You are Codex, a coding agent based on GPT-5. You and the user share the same wo {{ personality }} -## Tone and style -- Anything you say outside of tool use is shown to the user. Do not narrate abstractly; explain what you are doing and why, using plain language. -- Output will be rendered in a command line interface or minimal UI so keep responses tight, scannable, and low-noise. Generally avoid the use of emojis. You may format with GitHub-flavored Markdown. +# Working with the user + +You interact with the user through a terminal. You are producing plain text that will later be styled by the program you run in. Formatting should make results easy to scan, but not feel mechanical. Use judgment to decide how much structure adds value. Follow the formatting rules exactly. + +## Final answer formatting rules +- You may format with GitHub-flavored Markdown. +- Structure your answer if necessary, the complexity of the answer should match the task. If the task is simple, your answer should be a one-liner. Order sections from general to specific to supporting. - Never use nested bullets. Keep lists flat (single level). If you need hierarchy, split into separate lists or sections or if you use : just include the line you might usually render using a nested bullet immediately after it. For numbered lists, only use the `1. 2. 3.` style markers (with a period), never `1)`. -- When writing a final assistant response, state the solution first before explaining your answer. The complexity of the answer should match the task. If the task is simple, your answer should be short. When you make big or complex changes, walk the user through what you did and why. - Headers are optional, only use them when you think they are necessary. If you do use them, use short Title Case (1-3 words) wrapped in **…**. Don't add a blank line. +- Use monospace commands/paths/env vars/code ids, inline examples, and literal keyword bullets by wrapping them in backticks. - Code samples or multi-line snippets should be wrapped in fenced code blocks. Include an info string as often as possible. -- Never output the content of large files, just provide references. Use inline code to make file paths clickable; each reference should have a stand alone path, even if it's the same file. Paths may be absolute, workspace-relative, a//b/ diff-prefixed, or bare filename/suffix; locations may be :line[:column] or #Lline[Ccolumn] (1-based; column defaults to 1). Do not use file://, vscode://, or https://, and do not provide line ranges. Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\repo\project\main.rs:12:5 +- File References: When referencing files in your response follow the below rules: + * Use inline code to make file paths clickable. + * Each reference should have a stand alone path. Even if it's the same file. + * Accepted: absolute, workspace‑relative, a/ or b/ diff prefixes, or bare filename/suffix. + * Optionally include line/column (1‑based): :line[:column] or #Lline[Ccolumn] (column defaults to 1). + * Do not use URIs like file://, vscode://, or https://. + * Do not provide range of lines + * Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\repo\project\main.rs:12:5 +- Don’t use emojis. + + +## Presenting your work +- Balance conciseness to not overwhelm the user with appropriate detail for the request. Do not narrate abstractly; explain what you are doing and why. - The user does not see command execution outputs. When asked to show the output of a command (e.g. `git show`), relay the important details in your answer or summarize the key lines so the user understands the result. - Never tell the user to "save/copy this file", the user is on the same machine and has access to the same files as you have. +- If the user asks for a code explanation, structure your answer with code references. +- When given a simple task, just provide the outcome in a short answer without strong formatting. +- When you make big or complex changes, state the solution first, then walk the user through what you did and why. +- For casual chit-chat, just chat. +- Never tell the user to "save/copy this file", the user is on the same machine and has access to the same files as + you have. - If you weren't able to do something, for example run tests, tell the user. -- If there are natural next steps the user may want to take, suggest them at the end of your response. Do not make suggestions if there are no natural next steps. +- If there are natural next steps the user may want to take, suggest them at the end of your response. Do not make suggestions if there are no natural next steps. When suggesting multiple options, use numeric lists for the suggestions so the user can quickly respond with a single number. -# Code style +# General + +- When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.) + +## Editing constraints -- Follow the precedence rules user instructions > system / dev / user / AGENTS.md instructions > match local file conventions > instructions below. -- Use language-appropriate best practices. -- Optimize for clarity, readability, and maintainability. -- Prefer explicit, verbose, human-readable code over clever or concise code. -- Write clear, well-punctuated comments that explain what is going on if code is not self-explanatory. You should not add comments like "Assigns the value to the variable", but a brief comment might be useful ahead of a complex code block that the user would otherwise have to spend time parsing out. Usage of these comments should be rare. - Default to ASCII when editing or creating files. Only introduce non-ASCII or other Unicode characters when there is a clear justification and the file already uses them. - -# Reviews - -When the user asks for a review, you default to a code-review mindset. Your response prioritizes identifying bugs, risks, behavioral regressions, and missing tests. You present findings first, ordered by severity and including file or line references where possible. Open questions or assumptions follow. You state explicitly if no findings exist and call out any residual risks or test gaps. - -# Your environment - -## Using GIT - -- You may be working in a dirty git worktree. +- Add succinct code comments that explain what is going on if code is not self-explanatory. You should not add comments like "Assigns the value to the variable", but a brief comment might be useful ahead of a complex code block that the user would otherwise have to spend time parsing out. Usage of these comments should be rare. +- Try to use apply_patch for single file edits, but it is fine to explore other options to make the edit if it does not work well. Do not use apply_patch for changes that are auto-generated (i.e. generating package.json or running a lint or format command like gofmt) or when scripting is more efficient (such as search and replacing a string across a codebase). +- You may be in a dirty git worktree. * NEVER revert existing changes you did not make unless explicitly requested, since these changes were made by the user. * If asked to make a commit or code edits and there are unrelated changes to your work or changes that you didn't make in those files, don't revert those changes. * If the changes are in files you've touched recently, you should read carefully and understand how you can work with the changes rather than reverting them. * If the changes are in unrelated files, just ignore them and don't revert them. - Do not amend a commit unless explicitly requested to do so. -- While you are working, you might notice unexpected changes that you didn't make. It's likely the user made them. If this happens, STOP IMMEDIATELY and ask the user how they would like to proceed. -- Be cautious when using git. **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user. -- You struggle using the git interactive console. **ALWAYS** prefer using non-interactive git commands. +- While you are working, you might notice unexpected changes that you didn't make. If this happens, STOP IMMEDIATELY and ask the user how they would like to proceed. +- **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user. -## Agents.md +## Plan tool -- If the directory you are in has an AGENTS.md file, it is provided to you at the top, and you don't have to search for it. -- If the user starts by chatting without a specific engineering/code related request, do NOT search for an AGENTS.md. Only do so once there is a relevant request. +When using the planning tool: +- Skip using the planning tool for straightforward tasks (roughly the easiest 25%). +- Do not make single-step plans. +- When you made a plan, update it after having performed one of the sub-tasks that you shared on the plan. -# Tool use +## Special user requests -- Unless you are otherwise instructed, prefer using `rg` or `rg --files` respectively when searching because `rg` is much faster than alternatives like `grep`. If the `rg` command is not found, then use alternatives. -- Use the plan tool to explain to the user what you are going to do - - Only use it for more complex tasks, do not use it for straightforward tasks (roughly the easiest 25%). - - Do not make single-step plans. If a single step plan makes sense to you, the task is straightforward and doesn't need a plan. - - When you made a plan, update it after having performed one of the sub-tasks that you shared on the plan. -- Try to use apply_patch for single file edits, but it is fine to explore other options to make the edit if it does not work well. Do not use apply_patch for changes that are auto-generated (i.e. generating package.json or running a lint or format command like gofmt) or when scripting is more efficient (such as search and replacing a string across a codebase). +- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as `date`), you should do so. +- When the user asks for a review, you default to a code-review mindset. Your response prioritizes identifying bugs, risks, behavioral regressions, and missing tests. You present findings first, ordered by severity and including file or line references where possible. Open questions or assumptions follow. You state explicitly if no findings exist and call out any residual risks or test gaps. + +## Frontend tasks +When doing frontend design tasks, avoid collapsing into "AI slop" or safe, average-looking layouts. +Aim for interfaces that feel intentional, bold, and a bit surprising. +- Typography: Use expressive, purposeful fonts and avoid default stacks (Inter, Roboto, Arial, system). +- Color & Look: Choose a clear visual direction; define CSS variables; avoid purple-on-white defaults. No purple bias or dark mode bias. +- Motion: Use a few meaningful animations (page-load, staggered reveals) instead of generic micro-motions. +- Background: Don't rely on flat, single-color backgrounds; use gradients, shapes, or subtle patterns to build atmosphere. +- Overall: Avoid boilerplate layouts and interchangeable UI patterns. Vary themes, type families, and visual languages across outputs. +- Ensure the page loads properly on both desktop and mobile + +Exception: If working within an existing website or design system, preserve the established patterns, structure, and visual language. From 08a5ad95a888129ebaa48247113c06e2558900b7 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sun, 1 Feb 2026 23:32:07 -0800 Subject: [PATCH 061/139] fix(personality) prompt patch (#10375) ## Summary We had 2 typos in #10373 ## Testing - [x] unit tests pass --- .../model_instructions/gpt-5.2-codex_instructions_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md b/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md index 1af660d60a..23ad1ed697 100644 --- a/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md +++ b/codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md @@ -32,8 +32,6 @@ You interact with the user through a terminal. You are producing plain text that - When given a simple task, just provide the outcome in a short answer without strong formatting. - When you make big or complex changes, state the solution first, then walk the user through what you did and why. - For casual chit-chat, just chat. -- Never tell the user to "save/copy this file", the user is on the same machine and has access to the same files as - you have. - If you weren't able to do something, for example run tests, tell the user. - If there are natural next steps the user may want to take, suggest them at the end of your response. Do not make suggestions if there are no natural next steps. When suggesting multiple options, use numeric lists for the suggestions so the user can quickly respond with a single number. @@ -54,6 +52,7 @@ You interact with the user through a terminal. You are producing plain text that - Do not amend a commit unless explicitly requested to do so. - While you are working, you might notice unexpected changes that you didn't make. If this happens, STOP IMMEDIATELY and ask the user how they would like to proceed. - **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user. +- You struggle using the git interactive console. **ALWAYS** prefer using non-interactive git commands. ## Plan tool @@ -68,6 +67,7 @@ When using the planning tool: - When the user asks for a review, you default to a code-review mindset. Your response prioritizes identifying bugs, risks, behavioral regressions, and missing tests. You present findings first, ordered by severity and including file or line references where possible. Open questions or assumptions follow. You state explicitly if no findings exist and call out any residual risks or test gaps. ## Frontend tasks + When doing frontend design tasks, avoid collapsing into "AI slop" or safe, average-looking layouts. Aim for interfaces that feel intentional, bold, and a bit surprising. - Typography: Use expressive, purposeful fonts and avoid default stacks (Inter, Roboto, Arial, system). From 974355cfddb2402803d06076e60d12a2bd46bf37 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Sun, 1 Feb 2026 23:38:43 -0800 Subject: [PATCH 062/139] feat: vendor app-server protocol schema fixtures (#10371) Similar to what @sayan-oai did in openai/codex#8956 for `config.schema.json`, this PR updates the repo so that it includes the output of `codex app-server generate-json-schema` and `codex app-server generate-ts` and adds a test to verify it is in sync with the current code. Motivation: - This makes any schema changes introduced by a PR transparent during code review. - In particular, this should help us catch PRs that would introduce a non-backwards-compatible change to the app schema (eventually, this should also be enforced by tooling). - Once https://github.com/openai/codex/pull/10231 is in to formalize the notion of "experimental" fields, we can work on ensuring the non-experimental bits are backwards-compatible. `codex-rs/app-server-protocol/tests/schema_fixtures.rs` was added as the test and `just write-app-server-schema` can be use to generate the vendored schema files. Incidentally, when I run: ``` rg _ codex-rs/app-server-protocol/schema/typescript/v2 ``` I see a number of `snake_case` names that should be `camelCase`. --- codex-rs/Cargo.lock | 3 + codex-rs/app-server-protocol/BUILD.bazel | 1 + codex-rs/app-server-protocol/Cargo.toml | 3 + .../schema/json/ApplyPatchApprovalParams.json | 114 + .../json/ApplyPatchApprovalResponse.json | 73 + .../json/ChatgptAuthTokensRefreshParams.json | 33 + .../ChatgptAuthTokensRefreshResponse.json | 17 + .../schema/json/ClientNotification.json | 22 + .../schema/json/ClientRequest.json | 4350 +++++ ...CommandExecutionRequestApprovalParams.json | 174 + ...mmandExecutionRequestApprovalResponse.json | 72 + .../schema/json/DynamicToolCallParams.json | 27 + .../schema/json/DynamicToolCallResponse.json | 17 + .../schema/json/EventMsg.json | 7610 ++++++++ .../json/ExecCommandApprovalParams.json | 158 + .../json/ExecCommandApprovalResponse.json | 73 + .../json/FileChangeRequestApprovalParams.json | 35 + .../FileChangeRequestApprovalResponse.json | 47 + .../schema/json/FuzzyFileSearchParams.json | 26 + .../schema/json/FuzzyFileSearchResponse.json | 55 + .../schema/json/JSONRPCError.json | 48 + .../schema/json/JSONRPCErrorError.json | 19 + .../schema/json/JSONRPCMessage.json | 109 + .../schema/json/JSONRPCNotification.json | 15 + .../schema/json/JSONRPCRequest.json | 32 + .../schema/json/JSONRPCResponse.json | 29 + .../schema/json/RequestId.json | 13 + .../schema/json/ServerNotification.json | 8285 ++++++++ .../schema/json/ServerRequest.json | 792 + .../json/ToolRequestUserInputParams.json | 84 + .../json/ToolRequestUserInputResponse.json | 34 + .../codex_app_server_protocol.schemas.json | 16269 ++++++++++++++++ .../v1/AddConversationListenerParams.json | 22 + .../AddConversationSubscriptionResponse.json | 13 + .../json/v1/ArchiveConversationParams.json | 22 + .../json/v1/ArchiveConversationResponse.json | 5 + .../json/v1/AuthStatusChangeNotification.json | 46 + .../json/v1/CancelLoginChatGptParams.json | 13 + .../json/v1/CancelLoginChatGptResponse.json | 5 + .../json/v1/ExecOneOffCommandParams.json | 158 + .../json/v1/ExecOneOffCommandResponse.json | 22 + .../json/v1/ForkConversationParams.json | 159 + .../json/v1/ForkConversationResponse.json | 5358 +++++ .../schema/json/v1/GetAuthStatusParams.json | 19 + .../schema/json/v1/GetAuthStatusResponse.json | 57 + .../json/v1/GetConversationSummaryParams.json | 35 + .../v1/GetConversationSummaryResponse.json | 175 + .../schema/json/v1/GetUserAgentResponse.json | 13 + .../json/v1/GetUserSavedConfigResponse.json | 330 + .../schema/json/v1/GitDiffToRemoteParams.json | 13 + .../json/v1/GitDiffToRemoteResponse.json | 22 + .../schema/json/v1/InitializeParams.json | 36 + .../schema/json/v1/InitializeResponse.json | 13 + .../json/v1/InterruptConversationParams.json | 18 + .../v1/InterruptConversationResponse.json | 23 + .../json/v1/ListConversationsParams.json | 30 + .../json/v1/ListConversationsResponse.json | 184 + .../schema/json/v1/LoginApiKeyParams.json | 13 + .../schema/json/v1/LoginApiKeyResponse.json | 5 + .../v1/LoginChatGptCompleteNotification.json | 24 + .../schema/json/v1/LoginChatGptResponse.json | 17 + .../schema/json/v1/LogoutChatGptResponse.json | 5 + .../schema/json/v1/NewConversationParams.json | 125 + .../json/v1/NewConversationResponse.json | 48 + .../v1/RemoveConversationListenerParams.json | 13 + ...emoveConversationSubscriptionResponse.json | 5 + .../json/v1/ResumeConversationParams.json | 915 + .../json/v1/ResumeConversationResponse.json | 5358 +++++ .../schema/json/v1/SendUserMessageParams.json | 165 + .../json/v1/SendUserMessageResponse.json | 5 + .../schema/json/v1/SendUserTurnParams.json | 379 + .../schema/json/v1/SendUserTurnResponse.json | 5 + .../v1/SessionConfiguredNotification.json | 5380 +++++ .../schema/json/v1/SetDefaultModelParams.json | 37 + .../json/v1/SetDefaultModelResponse.json | 5 + .../schema/json/v1/UserInfoResponse.json | 13 + .../v2/AccountLoginCompletedNotification.json | 25 + .../AccountRateLimitsUpdatedNotification.json | 121 + .../json/v2/AccountUpdatedNotification.json | 45 + .../v2/AgentMessageDeltaNotification.json | 25 + .../schema/json/v2/AppsListParams.json | 23 + .../schema/json/v2/AppsListResponse.json | 74 + .../json/v2/CancelLoginAccountParams.json | 13 + .../json/v2/CancelLoginAccountResponse.json | 22 + .../json/v2/CollaborationModeListParams.json | 6 + .../v2/CollaborationModeListResponse.json | 93 + .../schema/json/v2/CommandExecParams.json | 147 + .../schema/json/v2/CommandExecResponse.json | 22 + ...mmandExecutionOutputDeltaNotification.json | 25 + .../json/v2/ConfigBatchWriteParams.json | 55 + .../schema/json/v2/ConfigReadParams.json | 18 + .../schema/json/v2/ConfigReadResponse.json | 604 + .../v2/ConfigRequirementsReadResponse.json | 76 + .../json/v2/ConfigValueWriteParams.json | 41 + .../json/v2/ConfigWarningNotification.json | 77 + .../schema/json/v2/ConfigWriteResponse.json | 237 + .../json/v2/ContextCompactedNotification.json | 18 + .../v2/DeprecationNoticeNotification.json | 21 + .../schema/json/v2/ErrorNotification.json | 197 + .../schema/json/v2/FeedbackUploadParams.json | 29 + .../json/v2/FeedbackUploadResponse.json | 13 + .../v2/FileChangeOutputDeltaNotification.json | 25 + .../schema/json/v2/GetAccountParams.json | 12 + .../json/v2/GetAccountRateLimitsResponse.json | 121 + .../schema/json/v2/GetAccountResponse.json | 83 + .../json/v2/ItemCompletedNotification.json | 1316 ++ .../json/v2/ItemStartedNotification.json | 1316 ++ .../json/v2/ListMcpServerStatusParams.json | 23 + .../json/v2/ListMcpServerStatusResponse.json | 325 + .../schema/json/v2/LoginAccountParams.json | 69 + .../schema/json/v2/LoginAccountResponse.json | 63 + .../schema/json/v2/LogoutAccountResponse.json | 5 + ...ServerOauthLoginCompletedNotification.json | 23 + .../json/v2/McpServerOauthLoginParams.json | 29 + .../json/v2/McpServerOauthLoginResponse.json | 13 + .../json/v2/McpServerRefreshResponse.json | 5 + .../v2/McpToolCallProgressNotification.json | 25 + .../schema/json/v2/ModelListParams.json | 23 + .../schema/json/v2/ModelListResponse.json | 94 + .../schema/json/v2/PlanDeltaNotification.json | 26 + .../RawResponseItemCompletedNotification.json | 770 + ...ReasoningSummaryPartAddedNotification.json | 26 + ...ReasoningSummaryTextDeltaNotification.json | 30 + .../v2/ReasoningTextDeltaNotification.json | 30 + .../schema/json/v2/ReviewStartParams.json | 129 + .../schema/json/v2/ReviewStartResponse.json | 1526 ++ .../json/v2/SkillsConfigWriteParams.json | 17 + .../json/v2/SkillsConfigWriteResponse.json | 13 + .../schema/json/v2/SkillsListParams.json | 18 + .../schema/json/v2/SkillsListResponse.json | 215 + .../v2/TerminalInteractionNotification.json | 29 + .../schema/json/v2/ThreadArchiveParams.json | 13 + .../schema/json/v2/ThreadArchiveResponse.json | 5 + .../schema/json/v2/ThreadForkParams.json | 98 + .../schema/json/v2/ThreadForkResponse.json | 1859 ++ .../schema/json/v2/ThreadListParams.json | 85 + .../schema/json/v2/ThreadListResponse.json | 1712 ++ .../json/v2/ThreadLoadedListParams.json | 23 + .../json/v2/ThreadLoadedListResponse.json | 24 + .../v2/ThreadNameUpdatedNotification.json | 19 + .../schema/json/v2/ThreadReadParams.json | 18 + .../schema/json/v2/ThreadReadResponse.json | 1702 ++ .../schema/json/v2/ThreadResumeParams.json | 872 + .../schema/json/v2/ThreadResumeResponse.json | 1859 ++ .../schema/json/v2/ThreadRollbackParams.json | 20 + .../json/v2/ThreadRollbackResponse.json | 1707 ++ .../schema/json/v2/ThreadSetNameParams.json | 17 + .../schema/json/v2/ThreadSetNameResponse.json | 5 + .../schema/json/v2/ThreadStartParams.json | 137 + .../schema/json/v2/ThreadStartResponse.json | 1859 ++ .../json/v2/ThreadStartedNotification.json | 1702 ++ .../ThreadTokenUsageUpdatedNotification.json | 77 + .../schema/json/v2/ThreadUnarchiveParams.json | 13 + .../json/v2/ThreadUnarchiveResponse.json | 1702 ++ .../json/v2/TurnCompletedNotification.json | 1525 ++ .../json/v2/TurnDiffUpdatedNotification.json | 22 + .../schema/json/v2/TurnInterruptParams.json | 17 + .../schema/json/v2/TurnInterruptResponse.json | 5 + .../json/v2/TurnPlanUpdatedNotification.json | 55 + .../schema/json/v2/TurnStartParams.json | 476 + .../schema/json/v2/TurnStartResponse.json | 1521 ++ .../json/v2/TurnStartedNotification.json | 1525 ++ ...ndowsWorldWritableWarningNotification.json | 26 + .../schema/typescript/AbsolutePathBuf.ts | 14 + .../AddConversationListenerParams.ts | 6 + .../AddConversationSubscriptionResponse.ts | 5 + .../schema/typescript/AgentMessageContent.ts | 5 + .../AgentMessageContentDeltaEvent.ts | 5 + .../typescript/AgentMessageDeltaEvent.ts | 5 + .../schema/typescript/AgentMessageEvent.ts | 5 + .../schema/typescript/AgentMessageItem.ts | 6 + .../typescript/AgentReasoningDeltaEvent.ts | 5 + .../schema/typescript/AgentReasoningEvent.ts | 5 + .../AgentReasoningRawContentDeltaEvent.ts | 5 + .../AgentReasoningRawContentEvent.ts | 5 + .../AgentReasoningSectionBreakEvent.ts | 5 + .../schema/typescript/AgentStatus.ts | 8 + .../schema/typescript/Annotations.ts | 9 + .../typescript/ApplyPatchApprovalParams.ts | 21 + .../ApplyPatchApprovalRequestEvent.ts | 23 + .../typescript/ApplyPatchApprovalResponse.ts | 6 + .../typescript/ArchiveConversationParams.ts | 6 + .../typescript/ArchiveConversationResponse.ts | 5 + .../schema/typescript/AskForApproval.ts | 9 + .../schema/typescript/AudioContent.ts | 9 + .../schema/typescript/AuthMode.ts | 8 + .../AuthStatusChangeNotification.ts | 9 + .../schema/typescript/BackgroundEventEvent.ts | 5 + .../schema/typescript/BlobResourceContents.ts | 5 + .../schema/typescript/ByteRange.ts | 13 + .../schema/typescript/CallToolResult.ts | 10 + .../typescript/CancelLoginChatGptParams.ts | 5 + .../typescript/CancelLoginChatGptResponse.ts | 5 + .../schema/typescript/ClientInfo.ts | 5 + .../schema/typescript/ClientNotification.ts | 5 + .../schema/typescript/ClientRequest.ts | 56 + .../schema/typescript/CodexErrorInfo.ts | 8 + .../CollabAgentInteractionBeginEvent.ts | 23 + .../CollabAgentInteractionEndEvent.ts | 28 + .../typescript/CollabAgentSpawnBeginEvent.ts | 19 + .../typescript/CollabAgentSpawnEndEvent.ts | 28 + .../typescript/CollabCloseBeginEvent.ts | 18 + .../schema/typescript/CollabCloseEndEvent.ts | 24 + .../typescript/CollabWaitingBeginEvent.ts | 18 + .../typescript/CollabWaitingEndEvent.ts | 19 + .../schema/typescript/CollaborationMode.ts | 10 + .../typescript/CollaborationModeMask.ts | 11 + .../schema/typescript/ContentBlock.ts | 10 + .../schema/typescript/ContentItem.ts | 5 + .../typescript/ContextCompactedEvent.ts | 5 + .../typescript/ContextCompactionItem.ts | 5 + .../schema/typescript/ConversationGitInfo.ts | 5 + .../schema/typescript/ConversationSummary.ts | 8 + .../schema/typescript/CreditsSnapshot.ts | 5 + .../schema/typescript/CustomPrompt.ts | 5 + .../typescript/DeprecationNoticeEvent.ts | 13 + .../typescript/DynamicToolCallRequest.ts | 6 + .../typescript/ElicitationRequestEvent.ts | 6 + .../schema/typescript/EmbeddedResource.ts | 13 + .../typescript/EmbeddedResourceResource.ts | 7 + .../schema/typescript/ErrorEvent.ts | 6 + .../schema/typescript/EventMsg.ts | 73 + .../typescript/ExecApprovalRequestEvent.ts | 32 + .../typescript/ExecCommandApprovalParams.ts | 12 + .../typescript/ExecCommandApprovalResponse.ts | 6 + .../typescript/ExecCommandBeginEvent.ts | 35 + .../schema/typescript/ExecCommandEndEvent.ts | 59 + .../typescript/ExecCommandOutputDeltaEvent.ts | 18 + .../schema/typescript/ExecCommandSource.ts | 5 + .../typescript/ExecOneOffCommandParams.ts | 6 + .../typescript/ExecOneOffCommandResponse.ts | 5 + .../schema/typescript/ExecOutputStream.ts | 5 + .../schema/typescript/ExecPolicyAmendment.ts | 12 + .../typescript/ExitedReviewModeEvent.ts | 6 + .../schema/typescript/FileChange.ts | 5 + .../schema/typescript/ForcedLoginMethod.ts | 5 + .../typescript/ForkConversationParams.ts | 7 + .../typescript/ForkConversationResponse.ts | 7 + .../FunctionCallOutputContentItem.ts | 9 + .../typescript/FunctionCallOutputPayload.ts | 15 + .../typescript/FuzzyFileSearchParams.ts | 5 + .../typescript/FuzzyFileSearchResponse.ts | 6 + .../typescript/FuzzyFileSearchResult.ts | 8 + .../schema/typescript/GetAuthStatusParams.ts | 5 + .../typescript/GetAuthStatusResponse.ts | 6 + .../GetConversationSummaryParams.ts | 6 + .../GetConversationSummaryResponse.ts | 6 + .../GetHistoryEntryResponseEvent.ts | 10 + .../schema/typescript/GetUserAgentResponse.ts | 5 + .../typescript/GetUserSavedConfigResponse.ts | 6 + .../schema/typescript/GhostCommit.ts | 8 + .../typescript/GitDiffToRemoteParams.ts | 5 + .../typescript/GitDiffToRemoteResponse.ts | 6 + .../schema/typescript/GitSha.ts | 5 + .../schema/typescript/HistoryEntry.ts | 5 + .../schema/typescript/ImageContent.ts | 9 + .../schema/typescript/InitializeParams.ts | 6 + .../schema/typescript/InitializeResponse.ts | 5 + .../schema/typescript/InputItem.ts | 10 + .../typescript/InterruptConversationParams.ts | 6 + .../InterruptConversationResponse.ts | 6 + .../schema/typescript/ItemCompletedEvent.ts | 7 + .../schema/typescript/ItemStartedEvent.ts | 7 + .../typescript/ListConversationsParams.ts | 5 + .../typescript/ListConversationsResponse.ts | 6 + .../ListCustomPromptsResponseEvent.ts | 9 + .../typescript/ListSkillsResponseEvent.ts | 9 + .../schema/typescript/LocalShellAction.ts | 6 + .../schema/typescript/LocalShellExecAction.ts | 5 + .../schema/typescript/LocalShellStatus.ts | 5 + .../schema/typescript/LoginApiKeyParams.ts | 5 + .../schema/typescript/LoginApiKeyResponse.ts | 5 + .../LoginChatGptCompleteNotification.ts | 8 + .../schema/typescript/LoginChatGptResponse.ts | 5 + .../typescript/LogoutChatGptResponse.ts | 5 + .../schema/typescript/McpAuthStatus.ts | 5 + .../schema/typescript/McpInvocation.ts | 18 + .../typescript/McpListToolsResponseEvent.ts | 25 + .../typescript/McpStartupCompleteEvent.ts | 6 + .../schema/typescript/McpStartupFailure.ts | 5 + .../schema/typescript/McpStartupStatus.ts | 5 + .../typescript/McpStartupUpdateEvent.ts | 14 + .../typescript/McpToolCallBeginEvent.ts | 10 + .../schema/typescript/McpToolCallEndEvent.ts | 15 + .../schema/typescript/ModeKind.ts | 8 + .../schema/typescript/NetworkAccess.ts | 8 + .../typescript/NewConversationParams.ts | 8 + .../typescript/NewConversationResponse.ts | 7 + .../schema/typescript/ParsedCommand.ts | 12 + .../schema/typescript/PatchApplyBeginEvent.ts | 23 + .../schema/typescript/PatchApplyEndEvent.ts | 31 + .../schema/typescript/Personality.ts | 5 + .../schema/typescript/PlanDeltaEvent.ts | 5 + .../schema/typescript/PlanItem.ts | 5 + .../schema/typescript/PlanItemArg.ts | 6 + .../schema/typescript/PlanType.ts | 5 + .../schema/typescript/Profile.ts | 9 + .../schema/typescript/RateLimitSnapshot.ts | 8 + .../schema/typescript/RateLimitWindow.ts | 17 + .../schema/typescript/RawResponseItemEvent.ts | 6 + .../typescript/ReasoningContentDeltaEvent.ts | 5 + .../schema/typescript/ReasoningEffort.ts | 8 + .../schema/typescript/ReasoningItem.ts | 5 + .../schema/typescript/ReasoningItemContent.ts | 5 + .../ReasoningItemReasoningSummary.ts | 5 + .../ReasoningRawContentDeltaEvent.ts | 5 + .../schema/typescript/ReasoningSummary.ts | 10 + .../RemoveConversationListenerParams.ts | 5 + .../RemoveConversationSubscriptionResponse.ts | 5 + .../schema/typescript/RequestId.ts | 5 + .../typescript/RequestUserInputEvent.ts | 15 + .../typescript/RequestUserInputQuestion.ts | 6 + .../RequestUserInputQuestionOption.ts | 5 + .../schema/typescript/Resource.ts | 9 + .../schema/typescript/ResourceLink.ts | 11 + .../schema/typescript/ResourceTemplate.ts | 9 + .../schema/typescript/ResponseItem.ts | 17 + .../typescript/ResumeConversationParams.ts | 8 + .../typescript/ResumeConversationResponse.ts | 7 + .../schema/typescript/ReviewCodeLocation.ts | 9 + .../schema/typescript/ReviewDecision.ts | 9 + .../schema/typescript/ReviewFinding.ts | 9 + .../schema/typescript/ReviewLineRange.ts | 8 + .../schema/typescript/ReviewOutputEvent.ts | 9 + .../schema/typescript/ReviewRequest.ts | 9 + .../schema/typescript/ReviewTarget.ts | 9 + .../schema/typescript/Role.ts | 8 + .../schema/typescript/SandboxMode.ts | 5 + .../schema/typescript/SandboxPolicy.ts | 35 + .../schema/typescript/SandboxSettings.ts | 6 + .../typescript/SendUserMessageParams.ts | 7 + .../typescript/SendUserMessageResponse.ts | 5 + .../schema/typescript/SendUserTurnParams.ts | 16 + .../schema/typescript/SendUserTurnResponse.ts | 5 + .../schema/typescript/ServerNotification.ts | 39 + .../schema/typescript/ServerRequest.ts | 16 + .../typescript/SessionConfiguredEvent.ts | 52 + .../SessionConfiguredNotification.ts | 8 + .../schema/typescript/SessionSource.ts | 6 + .../typescript/SetDefaultModelParams.ts | 6 + .../typescript/SetDefaultModelResponse.ts | 5 + .../schema/typescript/Settings.ts | 9 + .../schema/typescript/SkillDependencies.ts | 6 + .../schema/typescript/SkillErrorInfo.ts | 5 + .../schema/typescript/SkillInterface.ts | 5 + .../schema/typescript/SkillMetadata.ts | 12 + .../schema/typescript/SkillScope.ts | 5 + .../schema/typescript/SkillToolDependency.ts | 5 + .../schema/typescript/SkillsListEntry.ts | 7 + .../schema/typescript/StepStatus.ts | 5 + .../schema/typescript/StreamErrorEvent.ts | 12 + .../schema/typescript/SubAgentSource.ts | 6 + .../typescript/TerminalInteractionEvent.ts | 17 + .../schema/typescript/TextContent.ts | 9 + .../schema/typescript/TextElement.ts | 14 + .../schema/typescript/TextResourceContents.ts | 5 + .../schema/typescript/ThreadId.ts | 5 + .../typescript/ThreadNameUpdatedEvent.ts | 6 + .../typescript/ThreadRolledBackEvent.ts | 9 + .../schema/typescript/TokenCountEvent.ts | 7 + .../schema/typescript/TokenUsage.ts | 5 + .../schema/typescript/TokenUsageInfo.ts | 6 + .../schema/typescript/Tool.ts | 11 + .../schema/typescript/ToolAnnotations.ts | 15 + .../schema/typescript/ToolInputSchema.ts | 9 + .../schema/typescript/ToolOutputSchema.ts | 10 + .../schema/typescript/Tools.ts | 5 + .../schema/typescript/TurnAbortReason.ts | 5 + .../schema/typescript/TurnAbortedEvent.ts | 6 + .../schema/typescript/TurnCompleteEvent.ts | 5 + .../schema/typescript/TurnDiffEvent.ts | 5 + .../schema/typescript/TurnItem.ts | 11 + .../schema/typescript/TurnStartedEvent.ts | 6 + .../schema/typescript/UndoCompletedEvent.ts | 5 + .../schema/typescript/UndoStartedEvent.ts | 5 + .../schema/typescript/UpdatePlanArgs.ts | 10 + .../schema/typescript/UserInfoResponse.ts | 5 + .../schema/typescript/UserInput.ts | 16 + .../schema/typescript/UserMessageEvent.ts | 22 + .../schema/typescript/UserMessageItem.ts | 6 + .../schema/typescript/UserSavedConfig.ts | 14 + .../schema/typescript/Verbosity.ts | 9 + .../typescript/ViewImageToolCallEvent.ts | 13 + .../schema/typescript/WarningEvent.ts | 5 + .../schema/typescript/WebSearchAction.ts | 5 + .../schema/typescript/WebSearchBeginEvent.ts | 5 + .../schema/typescript/WebSearchEndEvent.ts | 6 + .../schema/typescript/WebSearchItem.ts | 6 + .../schema/typescript/WebSearchMode.ts | 5 + .../schema/typescript/index.ts | 229 + .../schema/typescript/serde_json/JsonValue.ts | 5 + .../schema/typescript/v2/Account.ts | 6 + .../v2/AccountLoginCompletedNotification.ts | 5 + .../AccountRateLimitsUpdatedNotification.ts | 6 + .../v2/AccountUpdatedNotification.ts | 6 + .../v2/AgentMessageDeltaNotification.ts | 5 + .../schema/typescript/v2/AnalyticsConfig.ts | 6 + .../schema/typescript/v2/AppInfo.ts | 5 + .../schema/typescript/v2/AppsListParams.ts | 13 + .../schema/typescript/v2/AppsListResponse.ts | 11 + .../schema/typescript/v2/AskForApproval.ts | 5 + .../schema/typescript/v2/ByteRange.ts | 5 + .../typescript/v2/CancelLoginAccountParams.ts | 5 + .../v2/CancelLoginAccountResponse.ts | 6 + .../typescript/v2/CancelLoginAccountStatus.ts | 5 + .../v2/ChatgptAuthTokensRefreshParams.ts | 16 + .../v2/ChatgptAuthTokensRefreshReason.ts | 5 + .../v2/ChatgptAuthTokensRefreshResponse.ts | 5 + .../schema/typescript/v2/CodexErrorInfo.ts | 11 + .../schema/typescript/v2/CollabAgentState.ts | 6 + .../schema/typescript/v2/CollabAgentStatus.ts | 5 + .../schema/typescript/v2/CollabAgentTool.ts | 5 + .../v2/CollabAgentToolCallStatus.ts | 5 + .../v2/CollaborationModeListParams.ts | 8 + .../v2/CollaborationModeListResponse.ts | 9 + .../schema/typescript/v2/CommandAction.ts | 5 + .../schema/typescript/v2/CommandExecParams.ts | 6 + .../typescript/v2/CommandExecResponse.ts | 5 + .../v2/CommandExecutionApprovalDecision.ts | 6 + ...CommandExecutionOutputDeltaNotification.ts | 5 + .../CommandExecutionRequestApprovalParams.ts | 27 + ...CommandExecutionRequestApprovalResponse.ts | 6 + .../typescript/v2/CommandExecutionStatus.ts | 5 + .../schema/typescript/v2/Config.ts | 17 + .../typescript/v2/ConfigBatchWriteParams.ts | 10 + .../schema/typescript/v2/ConfigEdit.ts | 7 + .../schema/typescript/v2/ConfigLayer.ts | 7 + .../typescript/v2/ConfigLayerMetadata.ts | 6 + .../schema/typescript/v2/ConfigLayerSource.ts | 16 + .../schema/typescript/v2/ConfigReadParams.ts | 11 + .../typescript/v2/ConfigReadResponse.ts | 8 + .../typescript/v2/ConfigRequirements.ts | 8 + .../v2/ConfigRequirementsReadResponse.ts | 10 + .../typescript/v2/ConfigValueWriteParams.ts | 11 + .../v2/ConfigWarningNotification.ts | 22 + .../typescript/v2/ConfigWriteResponse.ts | 12 + .../v2/ContextCompactedNotification.ts | 8 + .../schema/typescript/v2/CreditsSnapshot.ts | 5 + .../v2/DeprecationNoticeNotification.ts | 13 + .../typescript/v2/DynamicToolCallParams.ts | 6 + .../typescript/v2/DynamicToolCallResponse.ts | 5 + .../schema/typescript/v2/DynamicToolSpec.ts | 6 + .../schema/typescript/v2/ErrorNotification.ts | 6 + .../typescript/v2/ExecPolicyAmendment.ts | 5 + .../typescript/v2/FeedbackUploadParams.ts | 5 + .../typescript/v2/FeedbackUploadResponse.ts | 5 + .../v2/FileChangeApprovalDecision.ts | 5 + .../v2/FileChangeOutputDeltaNotification.ts | 5 + .../v2/FileChangeRequestApprovalParams.ts | 14 + .../v2/FileChangeRequestApprovalResponse.ts | 6 + .../schema/typescript/v2/FileUpdateChange.ts | 6 + .../schema/typescript/v2/GetAccountParams.ts | 13 + .../v2/GetAccountRateLimitsResponse.ts | 6 + .../typescript/v2/GetAccountResponse.ts | 6 + .../schema/typescript/v2/GitInfo.ts | 5 + .../v2/ItemCompletedNotification.ts | 6 + .../typescript/v2/ItemStartedNotification.ts | 6 + .../v2/ListMcpServerStatusParams.ts | 13 + .../v2/ListMcpServerStatusResponse.ts | 11 + .../typescript/v2/LoginAccountParams.ts | 17 + .../typescript/v2/LoginAccountResponse.ts | 9 + .../typescript/v2/LogoutAccountResponse.ts | 5 + .../schema/typescript/v2/McpAuthStatus.ts | 5 + ...cpServerOauthLoginCompletedNotification.ts | 5 + .../v2/McpServerOauthLoginParams.ts | 5 + .../v2/McpServerOauthLoginResponse.ts | 5 + .../typescript/v2/McpServerRefreshResponse.ts | 5 + .../schema/typescript/v2/McpServerStatus.ts | 9 + .../schema/typescript/v2/McpToolCallError.ts | 5 + .../v2/McpToolCallProgressNotification.ts | 5 + .../schema/typescript/v2/McpToolCallResult.ts | 7 + .../schema/typescript/v2/McpToolCallStatus.ts | 5 + .../schema/typescript/v2/MergeStrategy.ts | 5 + .../schema/typescript/v2/Model.ts | 7 + .../schema/typescript/v2/ModelListParams.ts | 13 + .../schema/typescript/v2/ModelListResponse.ts | 11 + .../schema/typescript/v2/NetworkAccess.ts | 5 + .../typescript/v2/OverriddenMetadata.ts | 7 + .../schema/typescript/v2/PatchApplyStatus.ts | 5 + .../schema/typescript/v2/PatchChangeKind.ts | 5 + .../typescript/v2/PlanDeltaNotification.ts | 9 + .../schema/typescript/v2/ProfileV2.ts | 11 + .../schema/typescript/v2/RateLimitSnapshot.ts | 8 + .../schema/typescript/v2/RateLimitWindow.ts | 5 + .../RawResponseItemCompletedNotification.ts | 6 + .../typescript/v2/ReasoningEffortOption.ts | 6 + .../ReasoningSummaryPartAddedNotification.ts | 5 + .../ReasoningSummaryTextDeltaNotification.ts | 5 + .../v2/ReasoningTextDeltaNotification.ts | 5 + .../typescript/v2/ResidencyRequirement.ts | 5 + .../schema/typescript/v2/ReviewDelivery.ts | 5 + .../schema/typescript/v2/ReviewStartParams.ts | 12 + .../typescript/v2/ReviewStartResponse.ts | 13 + .../schema/typescript/v2/ReviewTarget.ts | 9 + .../schema/typescript/v2/SandboxMode.ts | 5 + .../schema/typescript/v2/SandboxPolicy.ts | 7 + .../typescript/v2/SandboxWorkspaceWrite.ts | 5 + .../schema/typescript/v2/SessionSource.ts | 6 + .../schema/typescript/v2/SkillDependencies.ts | 6 + .../schema/typescript/v2/SkillErrorInfo.ts | 5 + .../schema/typescript/v2/SkillInterface.ts | 5 + .../schema/typescript/v2/SkillMetadata.ts | 12 + .../schema/typescript/v2/SkillScope.ts | 5 + .../typescript/v2/SkillToolDependency.ts | 5 + .../typescript/v2/SkillsConfigWriteParams.ts | 5 + .../v2/SkillsConfigWriteResponse.ts | 5 + .../schema/typescript/v2/SkillsListEntry.ts | 7 + .../schema/typescript/v2/SkillsListParams.ts | 13 + .../typescript/v2/SkillsListResponse.ts | 6 + .../v2/TerminalInteractionNotification.ts | 5 + .../schema/typescript/v2/TextElement.ts | 14 + .../schema/typescript/v2/TextPosition.ts | 13 + .../schema/typescript/v2/TextRange.ts | 6 + .../schema/typescript/v2/Thread.ts | 51 + .../typescript/v2/ThreadArchiveParams.ts | 5 + .../typescript/v2/ThreadArchiveResponse.ts | 5 + .../schema/typescript/v2/ThreadForkParams.ts | 26 + .../typescript/v2/ThreadForkResponse.ts | 9 + .../schema/typescript/v2/ThreadItem.ts | 81 + .../schema/typescript/v2/ThreadListParams.ts | 34 + .../typescript/v2/ThreadListResponse.ts | 11 + .../typescript/v2/ThreadLoadedListParams.ts | 13 + .../typescript/v2/ThreadLoadedListResponse.ts | 14 + .../v2/ThreadNameUpdatedNotification.ts | 5 + .../schema/typescript/v2/ThreadReadParams.ts | 9 + .../typescript/v2/ThreadReadResponse.ts | 6 + .../typescript/v2/ThreadResumeParams.ts | 36 + .../typescript/v2/ThreadResumeResponse.ts | 9 + .../typescript/v2/ThreadRollbackParams.ts | 12 + .../typescript/v2/ThreadRollbackResponse.ts | 14 + .../typescript/v2/ThreadSetNameParams.ts | 5 + .../typescript/v2/ThreadSetNameResponse.ts | 5 + .../schema/typescript/v2/ThreadSortKey.ts | 5 + .../schema/typescript/v2/ThreadSourceKind.ts | 5 + .../schema/typescript/v2/ThreadStartParams.ts | 17 + .../typescript/v2/ThreadStartResponse.ts | 9 + .../v2/ThreadStartedNotification.ts | 6 + .../schema/typescript/v2/ThreadTokenUsage.ts | 6 + .../v2/ThreadTokenUsageUpdatedNotification.ts | 6 + .../typescript/v2/ThreadUnarchiveParams.ts | 5 + .../typescript/v2/ThreadUnarchiveResponse.ts | 6 + .../typescript/v2/TokenUsageBreakdown.ts | 5 + .../v2/ToolRequestUserInputAnswer.ts | 8 + .../v2/ToolRequestUserInputOption.ts | 8 + .../v2/ToolRequestUserInputParams.ts | 9 + .../v2/ToolRequestUserInputQuestion.ts | 9 + .../v2/ToolRequestUserInputResponse.ts | 9 + .../schema/typescript/v2/ToolsV2.ts | 5 + .../schema/typescript/v2/Turn.ts | 18 + .../v2/TurnCompletedNotification.ts | 6 + .../v2/TurnDiffUpdatedNotification.ts | 9 + .../schema/typescript/v2/TurnError.ts | 6 + .../typescript/v2/TurnInterruptParams.ts | 5 + .../typescript/v2/TurnInterruptResponse.ts | 5 + .../schema/typescript/v2/TurnPlanStep.ts | 6 + .../typescript/v2/TurnPlanStepStatus.ts | 5 + .../v2/TurnPlanUpdatedNotification.ts | 6 + .../schema/typescript/v2/TurnStartParams.ts | 50 + .../schema/typescript/v2/TurnStartResponse.ts | 6 + .../typescript/v2/TurnStartedNotification.ts | 6 + .../schema/typescript/v2/TurnStatus.ts | 5 + .../schema/typescript/v2/UserInput.ts | 10 + ...WindowsWorldWritableWarningNotification.ts | 5 + .../schema/typescript/v2/WriteStatus.ts | 5 + .../schema/typescript/v2/index.ts | 175 + .../src/bin/write_schema_fixtures.rs | 32 + codex-rs/app-server-protocol/src/lib.rs | 3 + .../src/schema_fixtures.rs | 127 + .../tests/schema_fixtures.rs | 97 + justfile | 4 + 570 files changed, 91513 insertions(+) create mode 100644 codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/ClientNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/ClientRequest.json create mode 100644 codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/EventMsg.json create mode 100644 codex-rs/app-server-protocol/schema/json/ExecCommandApprovalParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/ExecCommandApprovalResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/FuzzyFileSearchParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/FuzzyFileSearchResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/JSONRPCError.json create mode 100644 codex-rs/app-server-protocol/schema/json/JSONRPCErrorError.json create mode 100644 codex-rs/app-server-protocol/schema/json/JSONRPCMessage.json create mode 100644 codex-rs/app-server-protocol/schema/json/JSONRPCNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/JSONRPCRequest.json create mode 100644 codex-rs/app-server-protocol/schema/json/JSONRPCResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/RequestId.json create mode 100644 codex-rs/app-server-protocol/schema/json/ServerNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/ServerRequest.json create mode 100644 codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/ToolRequestUserInputResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/AddConversationListenerParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/AddConversationSubscriptionResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/AuthStatusChangeNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ForkConversationParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GetUserAgentResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/InterruptConversationParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/InterruptConversationResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ListConversationsParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ListConversationsResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/LoginChatGptCompleteNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/LoginChatGptResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/LogoutChatGptResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/NewConversationParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/NewConversationResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/RemoveConversationListenerParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/RemoveConversationSubscriptionResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/SendUserMessageParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/SendUserMessageResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/SendUserTurnParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/SendUserTurnResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v1/UserInfoResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/AccountLoginCompletedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/AgentMessageDeltaNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/AppsListParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/CommandExecResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/CommandExecutionOutputDeltaNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ConfigBatchWriteParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ConfigReadParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ConfigValueWriteParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ConfigWarningNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ContextCompactedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/DeprecationNoticeNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/FileChangeOutputDeltaNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/GetAccountParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/LogoutAccountResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginCompletedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/McpServerRefreshResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/McpToolCallProgressNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ModelListParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/PlanDeltaNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryPartAddedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryTextDeltaNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ReasoningTextDeltaNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ReviewStartParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsListParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TerminalInteractionNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadNameUpdatedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadReadParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadTokenUsageUpdatedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnDiffUpdatedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnInterruptParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnInterruptResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnPlanUpdatedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/WindowsWorldWritableWarningNotification.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/AbsolutePathBuf.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Annotations.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AudioContent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AuthMode.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ByteRange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ClientInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ClientNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollaborationMode.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollaborationModeMask.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ContentItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ConversationGitInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ConversationSummary.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/EventMsg.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecPolicyAmendment.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/FileChange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ForcedLoginMethod.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputContentItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResult.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetAuthStatusParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetAuthStatusResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GhostCommit.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GitSha.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ImageContent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/InputItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LocalShellAction.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LocalShellExecAction.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LocalShellStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ModeKind.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ParsedCommand.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Personality.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanType.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Profile.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningEffort.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningItemContent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningItemReasoningSummary.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningSummary.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestId.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Resource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewDecision.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Role.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ServerRequest.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionSource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Settings.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillScope.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/StepStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SubAgentSource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TextContent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TextElement.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ThreadId.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Tool.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Tools.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserInput.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/Verbosity.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchAction.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchMode.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/index.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/serde_json/JsonValue.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/Account.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AccountLoginCompletedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AccountRateLimitsUpdatedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AgentMessageDeltaNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AnalyticsConfig.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AppsListResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AskForApproval.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ByteRange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshReason.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CodexErrorInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentState.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentTool.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentToolCallStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandAction.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandExecResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionApprovalDecision.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionOutputDeltaNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/Config.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigEdit.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayer.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerMetadata.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirementsReadResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigWarningNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ConfigWriteResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ContextCompactedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CreditsSnapshot.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeprecationNoticeNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSpec.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ErrorNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ExecPolicyAmendment.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/FileChangeApprovalDecision.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/FileChangeOutputDeltaNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/FileUpdateChange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/GetAccountParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/GetAccountRateLimitsResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/GitInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ItemCompletedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ItemStartedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/LogoutAccountResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpAuthStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginCompletedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpServerRefreshResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpServerStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallError.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallProgressNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/MergeStrategy.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/Model.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ModelListResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/NetworkAccess.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/OverriddenMetadata.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PatchApplyStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PatchChangeKind.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PlanDeltaNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/RateLimitWindow.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/RawResponseItemCompletedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReasoningEffortOption.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryPartAddedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryTextDeltaNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReasoningTextDeltaNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ResidencyRequirement.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReviewDelivery.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ReviewTarget.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SandboxMode.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SandboxWorkspaceWrite.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SessionSource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillDependencies.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillErrorInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillMetadata.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillScope.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillToolDependency.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsListEntry.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsListParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsListResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TerminalInteractionNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TextElement.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TextPosition.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TextRange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadListResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadNameUpdatedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadSortKey.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadSourceKind.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsage.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsageUpdatedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TokenUsageBreakdown.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputAnswer.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputOption.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputQuestion.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ToolsV2.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnCompletedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnDiffUpdatedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStep.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStepStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanUpdatedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnStartResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnStartedNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/WindowsWorldWritableWarningNotification.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/WriteStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/index.ts create mode 100644 codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs create mode 100644 codex-rs/app-server-protocol/src/schema_fixtures.rs create mode 100644 codex-rs/app-server-protocol/tests/schema_fixtures.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index cbe8aa6937..774cb9615f 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1126,12 +1126,15 @@ dependencies = [ "clap", "codex-protocol", "codex-utils-absolute-path", + "codex-utils-cargo-bin", "mcp-types", "pretty_assertions", "schemars 0.8.22", "serde", "serde_json", + "similar", "strum_macros 0.27.2", + "tempfile", "thiserror 2.0.17", "ts-rs", "uuid", diff --git a/codex-rs/app-server-protocol/BUILD.bazel b/codex-rs/app-server-protocol/BUILD.bazel index a95310aded..b95356e742 100644 --- a/codex-rs/app-server-protocol/BUILD.bazel +++ b/codex-rs/app-server-protocol/BUILD.bazel @@ -3,4 +3,5 @@ load("//:defs.bzl", "codex_rust_crate") codex_rust_crate( name = "app-server-protocol", crate_name = "codex_app_server_protocol", + test_data_extra = glob(["schema/**"], allow_empty = True), ) diff --git a/codex-rs/app-server-protocol/Cargo.toml b/codex-rs/app-server-protocol/Cargo.toml index 1c21bd6ea0..b3fc75bc50 100644 --- a/codex-rs/app-server-protocol/Cargo.toml +++ b/codex-rs/app-server-protocol/Cargo.toml @@ -27,4 +27,7 @@ uuid = { workspace = true, features = ["serde", "v7"] } [dev-dependencies] anyhow = { workspace = true } +codex-utils-cargo-bin = { workspace = true } pretty_assertions = { workspace = true } +similar = { workspace = true } +tempfile = { workspace = true } diff --git a/codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalParams.json b/codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalParams.json new file mode 100644 index 0000000000..da271c75ad --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalParams.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "ThreadId": { + "type": "string" + } + }, + "properties": { + "callId": { + "description": "Use to correlate this with [codex_core::protocol::PatchApplyBeginEvent] and [codex_core::protocol::PatchApplyEndEvent].", + "type": "string" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "fileChanges": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grantRoot": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "callId", + "conversationId", + "fileChanges" + ], + "title": "ApplyPatchApprovalParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalResponse.json b/codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalResponse.json new file mode 100644 index 0000000000..b00ac1184e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ApplyPatchApprovalResponse.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ReviewDecision": { + "description": "User's decision in response to an ExecApprovalRequest.", + "oneOf": [ + { + "description": "User has approved this command and the agent should execute it.", + "enum": [ + "approved" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.", + "properties": { + "approved_execpolicy_amendment": { + "properties": { + "proposed_execpolicy_amendment": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "proposed_execpolicy_amendment" + ], + "type": "object" + } + }, + "required": [ + "approved_execpolicy_amendment" + ], + "title": "ApprovedExecpolicyAmendmentReviewDecision", + "type": "object" + }, + { + "description": "User has approved this command and wants to automatically approve any future identical instances (`command` and `cwd` match exactly) for the remainder of the session.", + "enum": [ + "approved_for_session" + ], + "type": "string" + }, + { + "description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.", + "enum": [ + "denied" + ], + "type": "string" + }, + { + "description": "User has denied this command and the agent should not do anything until the user's next command.", + "enum": [ + "abort" + ], + "type": "string" + } + ] + } + }, + "properties": { + "decision": { + "$ref": "#/definitions/ReviewDecision" + } + }, + "required": [ + "decision" + ], + "title": "ApplyPatchApprovalResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshParams.json b/codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshParams.json new file mode 100644 index 0000000000..b3e828e80d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshParams.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ChatgptAuthTokensRefreshReason": { + "oneOf": [ + { + "description": "Codex attempted a backend request and received `401 Unauthorized`.", + "enum": [ + "unauthorized" + ], + "type": "string" + } + ] + } + }, + "properties": { + "previousAccountId": { + "description": "Workspace/account identifier that Codex was previously using.\n\nClients that manage multiple accounts/workspaces can use this as a hint to refresh the token for the correct workspace.\n\nThis may be `null` when the prior ID token did not include a workspace identifier (`chatgpt_account_id`) or when the token could not be parsed.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "$ref": "#/definitions/ChatgptAuthTokensRefreshReason" + } + }, + "required": [ + "reason" + ], + "title": "ChatgptAuthTokensRefreshParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshResponse.json b/codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshResponse.json new file mode 100644 index 0000000000..48d149492d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ChatgptAuthTokensRefreshResponse.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "accessToken": { + "type": "string" + }, + "idToken": { + "type": "string" + } + }, + "required": [ + "accessToken", + "idToken" + ], + "title": "ChatgptAuthTokensRefreshResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ClientNotification.json b/codex-rs/app-server-protocol/schema/json/ClientNotification.json new file mode 100644 index 0000000000..dde0b31fbd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ClientNotification.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "method": { + "enum": [ + "initialized" + ], + "title": "InitializedNotificationMethod", + "type": "string" + } + }, + "required": [ + "method" + ], + "title": "InitializedNotification", + "type": "object" + } + ], + "title": "ClientNotification" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json new file mode 100644 index 0000000000..5de02b85f0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -0,0 +1,4350 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AddConversationListenerParams": { + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "experimentalRawEvents": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "conversationId" + ], + "type": "object" + }, + "AppsListParams": { + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "ArchiveConversationParams": { + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "rolloutPath" + ], + "type": "object" + }, + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "AskForApproval2": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CancelLoginAccountParams": { + "properties": { + "loginId": { + "type": "string" + } + }, + "required": [ + "loginId" + ], + "type": "object" + }, + "CancelLoginChatGptParams": { + "properties": { + "loginId": { + "type": "string" + } + }, + "required": [ + "loginId" + ], + "type": "object" + }, + "ClientInfo": { + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "CollaborationModeListParams": { + "description": "EXPERIMENTAL - list collaboration mode presets.", + "type": "object" + }, + "CommandExecParams": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "timeoutMs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "command" + ], + "type": "object" + }, + "ConfigBatchWriteParams": { + "properties": { + "edits": { + "items": { + "$ref": "#/definitions/ConfigEdit" + }, + "type": "array" + }, + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "edits" + ], + "type": "object" + }, + "ConfigEdit": { + "properties": { + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "type": "object" + }, + "ConfigReadParams": { + "properties": { + "cwd": { + "description": "Optional working directory to resolve project config layers. If specified, return the effective config as seen from that directory (i.e., including any project layers between `cwd` and the project/repo root).", + "type": [ + "string", + "null" + ] + }, + "includeLayers": { + "default": false, + "type": "boolean" + } + }, + "type": "object" + }, + "ConfigValueWriteParams": { + "properties": { + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + }, + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "type": "object" + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "DynamicToolSpec": { + "properties": { + "description": { + "type": "string" + }, + "inputSchema": true, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "inputSchema", + "name" + ], + "type": "object" + }, + "ExecOneOffCommandParams": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy2" + }, + { + "type": "null" + } + ] + }, + "timeoutMs": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "command" + ], + "type": "object" + }, + "FeedbackUploadParams": { + "properties": { + "classification": { + "type": "string" + }, + "includeLogs": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "classification", + "includeLogs" + ], + "type": "object" + }, + "ForkConversationParams": { + "properties": { + "conversationId": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "overrides": { + "anyOf": [ + { + "$ref": "#/definitions/NewConversationParams" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "FuzzyFileSearchParams": { + "properties": { + "cancellationToken": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": "string" + }, + "roots": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "query", + "roots" + ], + "type": "object" + }, + "GetAccountParams": { + "properties": { + "refreshToken": { + "default": false, + "description": "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.", + "type": "boolean" + } + }, + "type": "object" + }, + "GetAuthStatusParams": { + "properties": { + "includeToken": { + "type": [ + "boolean", + "null" + ] + }, + "refreshToken": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "GetConversationSummaryParams": { + "anyOf": [ + { + "properties": { + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "rolloutPath" + ], + "title": "RolloutPathGetConversationSummaryParams", + "type": "object" + }, + { + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "conversationId" + ], + "title": "ConversationIdGetConversationSummaryParams", + "type": "object" + } + ] + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "GitDiffToRemoteParams": { + "properties": { + "cwd": { + "type": "string" + } + }, + "required": [ + "cwd" + ], + "type": "object" + }, + "InitializeParams": { + "properties": { + "clientInfo": { + "$ref": "#/definitions/ClientInfo" + } + }, + "required": [ + "clientInfo" + ], + "type": "object" + }, + "InputItem": { + "oneOf": [ + { + "properties": { + "data": { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/V1TextElement" + }, + "type": "array" + } + }, + "required": [ + "text" + ], + "type": "object" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "TextInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "image_url": { + "type": "string" + } + }, + "required": [ + "image_url" + ], + "type": "object" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "ImageInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "LocalImageInputItem", + "type": "object" + } + ] + }, + "InterruptConversationParams": { + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "conversationId" + ], + "type": "object" + }, + "ListConversationsParams": { + "properties": { + "cursor": { + "type": [ + "string", + "null" + ] + }, + "modelProviders": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "pageSize": { + "format": "uint", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "ListMcpServerStatusParams": { + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a server-defined value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "LoginAccountParams": { + "oneOf": [ + { + "properties": { + "apiKey": { + "type": "string" + }, + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyLoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "apiKey", + "type" + ], + "title": "ApiKeyLoginAccountParams", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "chatgpt" + ], + "title": "ChatgptLoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptLoginAccountParams", + "type": "object" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", + "properties": { + "accessToken": { + "description": "Access token (JWT) supplied by the client. This token is used for backend API requests.", + "type": "string" + }, + "idToken": { + "description": "ID token (JWT) supplied by the client.\n\nThis token is used for identity and account metadata (email, plan type, workspace id).", + "type": "string" + }, + "type": { + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensLoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "accessToken", + "idToken", + "type" + ], + "title": "ChatgptAuthTokensLoginAccountParams", + "type": "object" + } + ] + }, + "LoginApiKeyParams": { + "properties": { + "apiKey": { + "type": "string" + } + }, + "required": [ + "apiKey" + ], + "type": "object" + }, + "McpServerOauthLoginParams": { + "properties": { + "name": { + "type": "string" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "timeoutSecs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "MergeStrategy": { + "enum": [ + "replace", + "upsert" + ], + "type": "string" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "ModelListParams": { + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "NetworkAccess2": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "NewConversationParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval2" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "compactPrompt": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "includeApplyPatchTool": { + "type": [ + "boolean", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode2" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "Personality": { + "enum": [ + "friendly", + "pragmatic" + ], + "type": "string" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "RemoveConversationListenerParams": { + "properties": { + "subscriptionId": { + "type": "string" + } + }, + "required": [ + "subscriptionId" + ], + "type": "object" + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "ResumeConversationParams": { + "properties": { + "conversationId": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history": { + "items": { + "$ref": "#/definitions/ResponseItem" + }, + "type": [ + "array", + "null" + ] + }, + "overrides": { + "anyOf": [ + { + "$ref": "#/definitions/NewConversationParams" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ReviewDelivery": { + "enum": [ + "inline", + "detached" + ], + "type": "string" + }, + "ReviewStartParams": { + "properties": { + "delivery": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewDelivery" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`)." + }, + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "target", + "threadId" + ], + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions, equivalent to the old free-form prompt.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "SandboxMode2": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SandboxPolicy2": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicy2Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy2", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicy2Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy2", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess2" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicy2Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy2", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicy2Type", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy2", + "type": "object" + } + ] + }, + "SendUserMessageParams": { + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "items": { + "items": { + "$ref": "#/definitions/InputItem" + }, + "type": "array" + } + }, + "required": [ + "conversationId", + "items" + ], + "type": "object" + }, + "SendUserTurnParams": { + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval2" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "items": { + "items": { + "$ref": "#/definitions/InputItem" + }, + "type": "array" + }, + "model": { + "type": "string" + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "sandboxPolicy": { + "$ref": "#/definitions/SandboxPolicy2" + }, + "summary": { + "$ref": "#/definitions/ReasoningSummary" + } + }, + "required": [ + "approvalPolicy", + "conversationId", + "cwd", + "items", + "model", + "sandboxPolicy", + "summary" + ], + "type": "object" + }, + "SetDefaultModelParams": { + "properties": { + "model": { + "type": [ + "string", + "null" + ] + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "SkillsConfigWriteParams": { + "properties": { + "enabled": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "enabled", + "path" + ], + "type": "object" + }, + "SkillsListParams": { + "properties": { + "cwds": { + "description": "When empty, defaults to the current session working directory.", + "items": { + "type": "string" + }, + "type": "array" + }, + "forceReload": { + "description": "When true, bypass the skills cache and re-scan skills from disk.", + "type": "boolean" + } + }, + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "ThreadArchiveParams": { + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, + "ThreadForkParams": { + "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the forked thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Specify the rollout path to fork from. If specified, the thread_id param will be ignored.", + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadListParams": { + "properties": { + "archived": { + "description": "Optional archived filter; when set to true, only archived threads are returned. If false or null, only non-archived threads are returned.", + "type": [ + "boolean", + "null" + ] + }, + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "modelProviders": { + "description": "Optional provider filter; when set, only sessions recorded under these providers are returned. When present but empty, includes all providers.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "sortKey": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadSortKey" + }, + { + "type": "null" + } + ], + "description": "Optional sort key; defaults to created_at." + }, + "sourceKinds": { + "description": "Optional source filter; when set, only sessions from these source kinds are returned. When omitted or empty, defaults to interactive sources.", + "items": { + "$ref": "#/definitions/ThreadSourceKind" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, + "ThreadLoadedListParams": { + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to no limit.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "ThreadReadParams": { + "properties": { + "includeTurns": { + "default": false, + "description": "When true, include turns and their items from rollout history.", + "type": "boolean" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, + "ThreadResumeParams": { + "description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "history": { + "description": "[UNSTABLE] FOR CODEX CLOUD - DO NOT USE. If specified, the thread will be resumed with the provided history instead of loaded from disk.", + "items": { + "$ref": "#/definitions/ResponseItem" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the resumed thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Specify the rollout path to resume from. If specified, the thread_id param will be ignored.", + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, + "ThreadRollbackParams": { + "properties": { + "numTurns": { + "description": "The number of turns to drop from the end of the thread. Must be >= 1.\n\nThis only modifies the thread's history and does not revert local file changes that have been made by the agent. Clients are responsible for reverting these changes.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "numTurns", + "threadId" + ], + "type": "object" + }, + "ThreadSetNameParams": { + "properties": { + "name": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "name", + "threadId" + ], + "type": "object" + }, + "ThreadSortKey": { + "enum": [ + "created_at", + "updated_at" + ], + "type": "string" + }, + "ThreadSourceKind": { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "subAgent", + "subAgentReview", + "subAgentCompact", + "subAgentThreadSpawn", + "subAgentOther", + "unknown" + ], + "type": "string" + }, + "ThreadStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "dynamicTools": { + "items": { + "$ref": "#/definitions/DynamicToolSpec" + }, + "type": [ + "array", + "null" + ] + }, + "ephemeral": { + "type": [ + "boolean", + "null" + ] + }, + "experimentalRawEvents": { + "default": false, + "description": "If true, opt into emitting raw response items on the event stream.\n\nThis is for internal use only (e.g. Codex Cloud). (TODO): Figure out a better way to categorize internal / experimental events & protocols.", + "type": "boolean" + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ThreadUnarchiveParams": { + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, + "TurnInterruptParams": { + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "type": "object" + }, + "TurnStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "collaborationMode": { + "anyOf": [ + { + "$ref": "#/definitions/CollaborationMode" + }, + { + "type": "null" + } + ], + "description": "EXPERIMENTAL - set a pre-set collaboration mode. Takes precedence over model, reasoning_effort, and developer instructions if set." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "V1ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "V1TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/V1ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "description": "Request from the client to the server.", + "oneOf": [ + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "initialize" + ], + "title": "InitializeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/InitializeParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "InitializeRequest", + "type": "object" + }, + { + "description": "NEW APIs", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/start" + ], + "title": "Thread/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/resume" + ], + "title": "Thread/resumeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadResumeParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/resumeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/fork" + ], + "title": "Thread/forkRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadForkParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/forkRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/archive" + ], + "title": "Thread/archiveRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadArchiveParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/archiveRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/name/set" + ], + "title": "Thread/name/setRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadSetNameParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/name/setRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/unarchive" + ], + "title": "Thread/unarchiveRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadUnarchiveParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/unarchiveRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/rollback" + ], + "title": "Thread/rollbackRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadRollbackParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/rollbackRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/list" + ], + "title": "Thread/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/loaded/list" + ], + "title": "Thread/loaded/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadLoadedListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/loaded/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/read" + ], + "title": "Thread/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/list" + ], + "title": "Skills/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "app/list" + ], + "title": "App/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AppsListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "App/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/config/write" + ], + "title": "Skills/config/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsConfigWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/config/writeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/start" + ], + "title": "Turn/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/interrupt" + ], + "title": "Turn/interruptRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnInterruptParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/interruptRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "review/start" + ], + "title": "Review/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReviewStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Review/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "model/list" + ], + "title": "Model/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ModelListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Model/listRequest", + "type": "object" + }, + { + "description": "EXPERIMENTAL - list collaboration mode presets.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "collaborationMode/list" + ], + "title": "CollaborationMode/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CollaborationModeListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "CollaborationMode/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "mcpServer/oauth/login" + ], + "title": "McpServer/oauth/loginRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/McpServerOauthLoginParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "McpServer/oauth/loginRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/mcpServer/reload" + ], + "title": "Config/mcpServer/reloadRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Config/mcpServer/reloadRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "mcpServerStatus/list" + ], + "title": "McpServerStatus/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ListMcpServerStatusParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "McpServerStatus/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/login/start" + ], + "title": "Account/login/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoginAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/login/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/login/cancel" + ], + "title": "Account/login/cancelRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CancelLoginAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/login/cancelRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/logout" + ], + "title": "Account/logoutRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Account/logoutRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/rateLimits/read" + ], + "title": "Account/rateLimits/readRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Account/rateLimits/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "feedback/upload" + ], + "title": "Feedback/uploadRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FeedbackUploadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Feedback/uploadRequest", + "type": "object" + }, + { + "description": "Execute a command (argv vector) under the server's sandbox.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "command/exec" + ], + "title": "Command/execRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CommandExecParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Command/execRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/read" + ], + "title": "Config/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/value/write" + ], + "title": "Config/value/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigValueWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/value/writeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/batchWrite" + ], + "title": "Config/batchWriteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigBatchWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/batchWriteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "configRequirements/read" + ], + "title": "ConfigRequirements/readRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "ConfigRequirements/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/read" + ], + "title": "Account/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GetAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/readRequest", + "type": "object" + }, + { + "description": "DEPRECATED APIs below", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "newConversation" + ], + "title": "NewConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/NewConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "NewConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getConversationSummary" + ], + "title": "GetConversationSummaryRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GetConversationSummaryParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "GetConversationSummaryRequest", + "type": "object" + }, + { + "description": "List recorded Codex conversations (rollouts) with optional pagination and search.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "listConversations" + ], + "title": "ListConversationsRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ListConversationsParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ListConversationsRequest", + "type": "object" + }, + { + "description": "Resume a recorded Codex conversation from a rollout file.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "resumeConversation" + ], + "title": "ResumeConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ResumeConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ResumeConversationRequest", + "type": "object" + }, + { + "description": "Fork a recorded Codex conversation into a new session.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "forkConversation" + ], + "title": "ForkConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ForkConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ForkConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "archiveConversation" + ], + "title": "ArchiveConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ArchiveConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ArchiveConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "sendUserMessage" + ], + "title": "SendUserMessageRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SendUserMessageParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "SendUserMessageRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "sendUserTurn" + ], + "title": "SendUserTurnRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SendUserTurnParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "SendUserTurnRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "interruptConversation" + ], + "title": "InterruptConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/InterruptConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "InterruptConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "addConversationListener" + ], + "title": "AddConversationListenerRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AddConversationListenerParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "AddConversationListenerRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "removeConversationListener" + ], + "title": "RemoveConversationListenerRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/RemoveConversationListenerParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "RemoveConversationListenerRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "gitDiffToRemote" + ], + "title": "GitDiffToRemoteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GitDiffToRemoteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "GitDiffToRemoteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "loginApiKey" + ], + "title": "LoginApiKeyRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoginApiKeyParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "LoginApiKeyRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "loginChatGpt" + ], + "title": "LoginChatGptRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "LoginChatGptRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "cancelLoginChatGpt" + ], + "title": "CancelLoginChatGptRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CancelLoginChatGptParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "CancelLoginChatGptRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "logoutChatGpt" + ], + "title": "LogoutChatGptRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "LogoutChatGptRequest", + "type": "object" + }, + { + "description": "DEPRECATED in favor of GetAccount", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getAuthStatus" + ], + "title": "GetAuthStatusRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GetAuthStatusParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "GetAuthStatusRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getUserSavedConfig" + ], + "title": "GetUserSavedConfigRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "GetUserSavedConfigRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "setDefaultModel" + ], + "title": "SetDefaultModelRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SetDefaultModelParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "SetDefaultModelRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getUserAgent" + ], + "title": "GetUserAgentRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "GetUserAgentRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "userInfo" + ], + "title": "UserInfoRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "UserInfoRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "fuzzyFileSearch" + ], + "title": "FuzzyFileSearchRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "FuzzyFileSearchRequest", + "type": "object" + }, + { + "description": "Execute a command (argv vector) under the server's sandbox.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "execOneOffCommand" + ], + "title": "ExecOneOffCommandRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExecOneOffCommandParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExecOneOffCommandRequest", + "type": "object" + } + ], + "title": "ClientRequest" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json b/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json new file mode 100644 index 0000000000..9257aec839 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json @@ -0,0 +1,174 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + } + }, + "properties": { + "command": { + "description": "The command to be executed.", + "type": [ + "string", + "null" + ] + }, + "commandActions": { + "description": "Best-effort parsed command actions for friendly display.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": [ + "array", + "null" + ] + }, + "cwd": { + "description": "The command's working directory.", + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "proposedExecpolicyAmendment": { + "description": "Optional proposed execpolicy amendment to allow similar commands without prompting.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for network access).", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "threadId", + "turnId" + ], + "title": "CommandExecutionRequestApprovalParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalResponse.json b/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalResponse.json new file mode 100644 index 0000000000..fcc3eba786 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalResponse.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CommandExecutionApprovalDecision": { + "oneOf": [ + { + "description": "User approved the command.", + "enum": [ + "accept" + ], + "type": "string" + }, + { + "description": "User approved the command and future identical commands should run without prompting.", + "enum": [ + "acceptForSession" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "User approved the command, and wants to apply the proposed execpolicy amendment so future matching commands can run without prompting.", + "properties": { + "acceptWithExecpolicyAmendment": { + "properties": { + "execpolicy_amendment": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "execpolicy_amendment" + ], + "type": "object" + } + }, + "required": [ + "acceptWithExecpolicyAmendment" + ], + "title": "AcceptWithExecpolicyAmendmentCommandExecutionApprovalDecision", + "type": "object" + }, + { + "description": "User denied the command. The agent will continue the turn.", + "enum": [ + "decline" + ], + "type": "string" + }, + { + "description": "User denied the command. The turn will also be immediately interrupted.", + "enum": [ + "cancel" + ], + "type": "string" + } + ] + } + }, + "properties": { + "decision": { + "$ref": "#/definitions/CommandExecutionApprovalDecision" + } + }, + "required": [ + "decision" + ], + "title": "CommandExecutionRequestApprovalResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json b/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json new file mode 100644 index 0000000000..2ccc94ca06 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "threadId", + "tool", + "turnId" + ], + "title": "DynamicToolCallParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json b/codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json new file mode 100644 index 0000000000..662d3bda4f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "output": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "output", + "success" + ], + "title": "DynamicToolCallResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json new file mode 100644 index 0000000000..407626d82d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -0,0 +1,7610 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AgentMessageContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Text" + ], + "title": "TextAgentMessageContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextAgentMessageContent", + "type": "object" + } + ] + }, + "AgentStatus": { + "description": "Agent lifecycle status, derived from emitted events.", + "oneOf": [ + { + "description": "Agent is waiting for initialization.", + "enum": [ + "pending_init" + ], + "type": "string" + }, + { + "description": "Agent is currently running.", + "enum": [ + "running" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Agent is done. Contains the final assistant message.", + "properties": { + "completed": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "completed" + ], + "title": "CompletedAgentStatus", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Agent encountered an error.", + "properties": { + "errored": { + "type": "string" + } + }, + "required": [ + "errored" + ], + "title": "ErroredAgentStatus", + "type": "object" + }, + { + "description": "Agent has been shutdown.", + "enum": [ + "shutdown" + ], + "type": "string" + }, + { + "description": "Agent is not found.", + "enum": [ + "not_found" + ], + "type": "string" + } + ] + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "Codex errors that we expose to clients.", + "oneOf": [ + { + "enum": [ + "context_window_exceeded", + "usage_limit_exceeded", + "internal_server_error", + "unauthorized", + "bad_request", + "sandbox_error", + "thread_rollback_failed", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "model_cap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "model_cap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "http_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "http_connection_failed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "response_stream_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_connection_failed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turnbefore completion.", + "properties": { + "response_stream_disconnected": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_disconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "response_too_many_failed_attempts": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_too_many_failed_attempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "has_credits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "has_credits", + "unlimited" + ], + "type": "object" + }, + "CustomPrompt": { + "properties": { + "argument_hint": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "Duration": { + "properties": { + "nanos": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "secs": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "nanos", + "secs" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "EventMsg": { + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": "code" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the associated exec call, if available.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + } + ] + }, + "ExecCommandSource": { + "enum": [ + "agent", + "user_shell", + "unified_exec_startup", + "unified_exec_interaction" + ], + "type": "string" + }, + "ExecOutputStream": { + "enum": [ + "stdout", + "stderr" + ], + "type": "string" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "HistoryEntry": { + "properties": { + "conversation_id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "ts": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "conversation_id", + "text", + "ts" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "not_logged_in", + "bearer_token", + "o_auth" + ], + "type": "string" + }, + "McpInvocation": { + "properties": { + "arguments": { + "description": "Arguments to the tool call." + }, + "server": { + "description": "Name of the MCP server as defined in the config.", + "type": "string" + }, + "tool": { + "description": "Name of the tool as given by the MCP server.", + "type": "string" + } + }, + "required": [ + "server", + "tool" + ], + "type": "object" + }, + "McpStartupFailure": { + "properties": { + "error": { + "type": "string" + }, + "server": { + "type": "string" + } + }, + "required": [ + "error", + "server" + ], + "type": "object" + }, + "McpStartupStatus": { + "oneOf": [ + { + "properties": { + "state": { + "enum": [ + "starting" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "ready" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus2", + "type": "object" + }, + { + "properties": { + "error": { + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "error", + "state" + ], + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "cancelled" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus3", + "type": "object" + } + ] + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "PlanItemArg": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/definitions/StepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "plan_type": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resets_at": { + "description": "Unix timestamp (seconds since epoch) when the window resets.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "used_percent": { + "description": "Percentage (0-100) of the window that has been consumed.", + "format": "double", + "type": "number" + }, + "window_minutes": { + "description": "Rolling window duration, in minutes.", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "used_percent" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "RequestUserInputQuestion": { + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestionOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "RequestUserInputQuestionOption": { + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "Result_of_CallToolResult_or_String": { + "oneOf": [ + { + "properties": { + "Ok": { + "$ref": "#/definitions/CallToolResult" + } + }, + "required": [ + "Ok" + ], + "title": "OkResult_of_CallToolResult_or_String", + "type": "object" + }, + { + "properties": { + "Err": { + "type": "string" + } + }, + "required": [ + "Err" + ], + "title": "ErrResult_of_CallToolResult_or_String", + "type": "object" + } + ] + }, + "ReviewCodeLocation": { + "description": "Location of the code related to a review finding.", + "properties": { + "absolute_file_path": { + "type": "string" + }, + "line_range": { + "$ref": "#/definitions/ReviewLineRange" + } + }, + "required": [ + "absolute_file_path", + "line_range" + ], + "type": "object" + }, + "ReviewFinding": { + "description": "A single review finding describing an observed issue or recommendation.", + "properties": { + "body": { + "type": "string" + }, + "code_location": { + "$ref": "#/definitions/ReviewCodeLocation" + }, + "confidence_score": { + "format": "float", + "type": "number" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "body", + "code_location", + "confidence_score", + "priority", + "title" + ], + "type": "object" + }, + "ReviewLineRange": { + "description": "Inclusive line range in a file associated with the finding.", + "properties": { + "end": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ReviewOutputEvent": { + "description": "Structured review result produced by a child review session.", + "properties": { + "findings": { + "items": { + "$ref": "#/definitions/ReviewFinding" + }, + "type": "array" + }, + "overall_confidence_score": { + "format": "float", + "type": "number" + }, + "overall_correctness": { + "type": "string" + }, + "overall_explanation": { + "type": "string" + } + }, + "required": [ + "findings", + "overall_confidence_score", + "overall_correctness", + "overall_explanation" + ], + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions provided by the user.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brand_color": { + "type": [ + "string", + "null" + ] + }, + "default_prompt": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "icon_large": { + "type": [ + "string", + "null" + ] + }, + "icon_small": { + "type": [ + "string", + "null" + ] + }, + "short_description": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "short_description": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "StepStatus": { + "enum": [ + "pending", + "in_progress", + "completed" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byte_range": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byte_range" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "TokenUsage": { + "properties": { + "cached_input_tokens": { + "format": "int64", + "type": "integer" + }, + "input_tokens": { + "format": "int64", + "type": "integer" + }, + "output_tokens": { + "format": "int64", + "type": "integer" + }, + "reasoning_output_tokens": { + "format": "int64", + "type": "integer" + }, + "total_tokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cached_input_tokens", + "input_tokens", + "output_tokens", + "reasoning_output_tokens", + "total_tokens" + ], + "type": "object" + }, + "TokenUsageInfo": { + "properties": { + "last_token_usage": { + "$ref": "#/definitions/TokenUsage" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total_token_usage": { + "$ref": "#/definitions/TokenUsage" + } + }, + "required": [ + "last_token_usage", + "total_token_usage" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + }, + "TurnItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "UserMessage" + ], + "title": "UserMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageTurnItem", + "type": "object" + }, + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/AgentMessageContent" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "AgentMessage" + ], + "title": "AgentMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "AgentMessageTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Plan" + ], + "title": "PlanTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "raw_content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "summary_text": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "Reasoning" + ], + "title": "ReasoningTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary_text", + "type" + ], + "title": "ReasoningTurnItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "WebSearch" + ], + "title": "WebSearchTurnItemType", + "type": "string" + } + }, + "required": [ + "action", + "id", + "query", + "type" + ], + "title": "WebSearchTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "ContextCompaction" + ], + "title": "ContextCompactionTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionTurnItem", + "type": "object" + } + ] + }, + "UserInput": { + "description": "User input", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` that should be treated as special elements. These are byte ranges into the UTF-8 `text` buffer and are used to render or persist rich input markers (e.g., image placeholders) across history and resume without mutating the literal text.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "description": "Pre‑encoded data: URI image.", + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "description": "Local image path provided by the user. This will be converted to an `Image` variant (base64 data URL) during request serialization.", + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "local_image" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "description": "Skill selected by the user (name + path to SKILL.md).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "description": "Explicit mention selected by the user (name + app://connector id).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": "code" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the associated exec call, if available.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + } + ], + "title": "EventMsg" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ExecCommandApprovalParams.json b/codex-rs/app-server-protocol/schema/json/ExecCommandApprovalParams.json new file mode 100644 index 0000000000..977b1626a0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ExecCommandApprovalParams.json @@ -0,0 +1,158 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "ThreadId": { + "type": "string" + } + }, + "properties": { + "callId": { + "description": "Use to correlate this with [codex_core::protocol::ExecCommandBeginEvent] and [codex_core::protocol::ExecCommandEndEvent].", + "type": "string" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "parsedCmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "reason": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "callId", + "command", + "conversationId", + "cwd", + "parsedCmd" + ], + "title": "ExecCommandApprovalParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ExecCommandApprovalResponse.json b/codex-rs/app-server-protocol/schema/json/ExecCommandApprovalResponse.json new file mode 100644 index 0000000000..1f278291a2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ExecCommandApprovalResponse.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ReviewDecision": { + "description": "User's decision in response to an ExecApprovalRequest.", + "oneOf": [ + { + "description": "User has approved this command and the agent should execute it.", + "enum": [ + "approved" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.", + "properties": { + "approved_execpolicy_amendment": { + "properties": { + "proposed_execpolicy_amendment": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "proposed_execpolicy_amendment" + ], + "type": "object" + } + }, + "required": [ + "approved_execpolicy_amendment" + ], + "title": "ApprovedExecpolicyAmendmentReviewDecision", + "type": "object" + }, + { + "description": "User has approved this command and wants to automatically approve any future identical instances (`command` and `cwd` match exactly) for the remainder of the session.", + "enum": [ + "approved_for_session" + ], + "type": "string" + }, + { + "description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.", + "enum": [ + "denied" + ], + "type": "string" + }, + { + "description": "User has denied this command and the agent should not do anything until the user's next command.", + "enum": [ + "abort" + ], + "type": "string" + } + ] + } + }, + "properties": { + "decision": { + "$ref": "#/definitions/ReviewDecision" + } + }, + "required": [ + "decision" + ], + "title": "ExecCommandApprovalResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalParams.json b/codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalParams.json new file mode 100644 index 0000000000..f52e98cd0d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalParams.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "grantRoot": { + "description": "[UNSTABLE] When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "threadId", + "turnId" + ], + "title": "FileChangeRequestApprovalParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalResponse.json b/codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalResponse.json new file mode 100644 index 0000000000..f20035e3d7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/FileChangeRequestApprovalResponse.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "FileChangeApprovalDecision": { + "oneOf": [ + { + "description": "User approved the file changes.", + "enum": [ + "accept" + ], + "type": "string" + }, + { + "description": "User approved the file changes and future changes to the same files should run without prompting.", + "enum": [ + "acceptForSession" + ], + "type": "string" + }, + { + "description": "User denied the file changes. The agent will continue the turn.", + "enum": [ + "decline" + ], + "type": "string" + }, + { + "description": "User denied the file changes. The turn will also be immediately interrupted.", + "enum": [ + "cancel" + ], + "type": "string" + } + ] + } + }, + "properties": { + "decision": { + "$ref": "#/definitions/FileChangeApprovalDecision" + } + }, + "required": [ + "decision" + ], + "title": "FileChangeRequestApprovalResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/FuzzyFileSearchParams.json b/codex-rs/app-server-protocol/schema/json/FuzzyFileSearchParams.json new file mode 100644 index 0000000000..3a72939de4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/FuzzyFileSearchParams.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cancellationToken": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": "string" + }, + "roots": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "query", + "roots" + ], + "title": "FuzzyFileSearchParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/FuzzyFileSearchResponse.json b/codex-rs/app-server-protocol/schema/json/FuzzyFileSearchResponse.json new file mode 100644 index 0000000000..3309b9fb5d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/FuzzyFileSearchResponse.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "FuzzyFileSearchResult": { + "description": "Superset of [`codex_file_search::FileMatch`]", + "properties": { + "file_name": { + "type": "string" + }, + "indices": { + "items": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": [ + "array", + "null" + ] + }, + "path": { + "type": "string" + }, + "root": { + "type": "string" + }, + "score": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "file_name", + "path", + "root", + "score" + ], + "type": "object" + } + }, + "properties": { + "files": { + "items": { + "$ref": "#/definitions/FuzzyFileSearchResult" + }, + "type": "array" + } + }, + "required": [ + "files" + ], + "title": "FuzzyFileSearchResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/JSONRPCError.json b/codex-rs/app-server-protocol/schema/json/JSONRPCError.json new file mode 100644 index 0000000000..6db5d1a7fa --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/JSONRPCError.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "JSONRPCErrorError": { + "properties": { + "code": { + "format": "int64", + "type": "integer" + }, + "data": true, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + } + }, + "description": "A response to a request that indicates an error occurred.", + "properties": { + "error": { + "$ref": "#/definitions/JSONRPCErrorError" + }, + "id": { + "$ref": "#/definitions/RequestId" + } + }, + "required": [ + "error", + "id" + ], + "title": "JSONRPCError", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/JSONRPCErrorError.json b/codex-rs/app-server-protocol/schema/json/JSONRPCErrorError.json new file mode 100644 index 0000000000..932ef33c9a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/JSONRPCErrorError.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "code": { + "format": "int64", + "type": "integer" + }, + "data": true, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "title": "JSONRPCErrorError", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/JSONRPCMessage.json b/codex-rs/app-server-protocol/schema/json/JSONRPCMessage.json new file mode 100644 index 0000000000..b2f6c31011 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/JSONRPCMessage.json @@ -0,0 +1,109 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCRequest" + }, + { + "$ref": "#/definitions/JSONRPCNotification" + }, + { + "$ref": "#/definitions/JSONRPCResponse" + }, + { + "$ref": "#/definitions/JSONRPCError" + } + ], + "definitions": { + "JSONRPCError": { + "description": "A response to a request that indicates an error occurred.", + "properties": { + "error": { + "$ref": "#/definitions/JSONRPCErrorError" + }, + "id": { + "$ref": "#/definitions/RequestId" + } + }, + "required": [ + "error", + "id" + ], + "type": "object" + }, + "JSONRPCErrorError": { + "properties": { + "code": { + "format": "int64", + "type": "integer" + }, + "data": true, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "JSONRPCNotification": { + "description": "A notification which does not expect a response.", + "properties": { + "method": { + "type": "string" + }, + "params": true + }, + "required": [ + "method" + ], + "type": "object" + }, + "JSONRPCRequest": { + "description": "A request that expects a response.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "type": "string" + }, + "params": true + }, + "required": [ + "id", + "method" + ], + "type": "object" + }, + "JSONRPCResponse": { + "description": "A successful (non-error) response to a request.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "result": true + }, + "required": [ + "id", + "result" + ], + "type": "object" + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + } + }, + "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.", + "title": "JSONRPCMessage" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/JSONRPCNotification.json b/codex-rs/app-server-protocol/schema/json/JSONRPCNotification.json new file mode 100644 index 0000000000..2ddd61a8ca --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/JSONRPCNotification.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A notification which does not expect a response.", + "properties": { + "method": { + "type": "string" + }, + "params": true + }, + "required": [ + "method" + ], + "title": "JSONRPCNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/JSONRPCRequest.json b/codex-rs/app-server-protocol/schema/json/JSONRPCRequest.json new file mode 100644 index 0000000000..cc7a16f1ba --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/JSONRPCRequest.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + } + }, + "description": "A request that expects a response.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "type": "string" + }, + "params": true + }, + "required": [ + "id", + "method" + ], + "title": "JSONRPCRequest", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/JSONRPCResponse.json b/codex-rs/app-server-protocol/schema/json/JSONRPCResponse.json new file mode 100644 index 0000000000..9f1ec29548 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/JSONRPCResponse.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + } + }, + "description": "A successful (non-error) response to a request.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "result": true + }, + "required": [ + "id", + "result" + ], + "title": "JSONRPCResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/RequestId.json b/codex-rs/app-server-protocol/schema/json/RequestId.json new file mode 100644 index 0000000000..d0fa43db82 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/RequestId.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ], + "title": "RequestId" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json new file mode 100644 index 0000000000..b6777d5c8b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -0,0 +1,8285 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AccountLoginCompletedNotification": { + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "type": "object" + }, + "AccountRateLimitsUpdatedNotification": { + "properties": { + "rateLimits": { + "$ref": "#/definitions/RateLimitSnapshot" + } + }, + "required": [ + "rateLimits" + ], + "type": "object" + }, + "AccountUpdatedNotification": { + "properties": { + "authMode": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "AgentMessageContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Text" + ], + "title": "TextAgentMessageContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextAgentMessageContent", + "type": "object" + } + ] + }, + "AgentMessageDeltaNotification": { + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "type": "object" + }, + "AgentStatus": { + "description": "Agent lifecycle status, derived from emitted events.", + "oneOf": [ + { + "description": "Agent is waiting for initialization.", + "enum": [ + "pending_init" + ], + "type": "string" + }, + { + "description": "Agent is currently running.", + "enum": [ + "running" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Agent is done. Contains the final assistant message.", + "properties": { + "completed": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "completed" + ], + "title": "CompletedAgentStatus", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Agent encountered an error.", + "properties": { + "errored": { + "type": "string" + } + }, + "required": [ + "errored" + ], + "title": "ErroredAgentStatus", + "type": "object" + }, + { + "description": "Agent has been shutdown.", + "enum": [ + "shutdown" + ], + "type": "string" + }, + { + "description": "Agent is not found.", + "enum": [ + "not_found" + ], + "type": "string" + } + ] + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "enum": [ + "apikey" + ], + "type": "string" + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "enum": [ + "chatgpt" + ], + "type": "string" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "enum": [ + "chatgptAuthTokens" + ], + "type": "string" + } + ] + }, + "AuthStatusChangeNotification": { + "description": "Deprecated notification. Use AccountUpdatedNotification instead.", + "properties": { + "authMethod": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ByteRange2": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CodexErrorInfo2": { + "description": "Codex errors that we expose to clients.", + "oneOf": [ + { + "enum": [ + "context_window_exceeded", + "usage_limit_exceeded", + "internal_server_error", + "unauthorized", + "bad_request", + "sandbox_error", + "thread_rollback_failed", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "model_cap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "model_cap" + ], + "title": "ModelCapCodexErrorInfo2", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "http_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "http_connection_failed" + ], + "title": "HttpConnectionFailedCodexErrorInfo2", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "response_stream_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_connection_failed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo2", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turnbefore completion.", + "properties": { + "response_stream_disconnected": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_disconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo2", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "response_too_many_failed_attempts": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_too_many_failed_attempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo2", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionOutputDeltaNotification": { + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "type": "object" + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ConfigWarningNotification": { + "properties": { + "details": { + "description": "Optional extra guidance or error details.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Optional path to the config file that triggered the warning.", + "type": [ + "string", + "null" + ] + }, + "range": { + "anyOf": [ + { + "$ref": "#/definitions/TextRange" + }, + { + "type": "null" + } + ], + "description": "Optional range for the error location inside the config file." + }, + "summary": { + "description": "Concise summary of the warning.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "type": "object" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "ContextCompactedNotification": { + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "type": "object" + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "hasCredits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "hasCredits", + "unlimited" + ], + "type": "object" + }, + "CreditsSnapshot2": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "has_credits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "has_credits", + "unlimited" + ], + "type": "object" + }, + "CustomPrompt": { + "properties": { + "argument_hint": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "DeprecationNoticeNotification": { + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "type": "object" + }, + "Duration": { + "properties": { + "nanos": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "secs": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "nanos", + "secs" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "ErrorNotification": { + "properties": { + "error": { + "$ref": "#/definitions/TurnError" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "willRetry": { + "type": "boolean" + } + }, + "required": [ + "error", + "threadId", + "turnId", + "willRetry" + ], + "type": "object" + }, + "EventMsg": { + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo2" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": "code" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot2" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement2" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction2" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the associated exec call, if available.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo2" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + } + ] + }, + "ExecCommandSource": { + "enum": [ + "agent", + "user_shell", + "unified_exec_startup", + "unified_exec_interaction" + ], + "type": "string" + }, + "ExecOutputStream": { + "enum": [ + "stdout", + "stderr" + ], + "type": "string" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FileChangeOutputDeltaNotification": { + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "type": "object" + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "HistoryEntry": { + "properties": { + "conversation_id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "ts": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "conversation_id", + "text", + "ts" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "ItemCompletedNotification": { + "properties": { + "item": { + "$ref": "#/definitions/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "type": "object" + }, + "ItemStartedNotification": { + "properties": { + "item": { + "$ref": "#/definitions/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "LoginChatGptCompleteNotification": { + "description": "Deprecated in favor of AccountLoginCompletedNotification.", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "loginId", + "success" + ], + "type": "object" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "not_logged_in", + "bearer_token", + "o_auth" + ], + "type": "string" + }, + "McpInvocation": { + "properties": { + "arguments": { + "description": "Arguments to the tool call." + }, + "server": { + "description": "Name of the MCP server as defined in the config.", + "type": "string" + }, + "tool": { + "description": "Name of the tool as given by the MCP server.", + "type": "string" + } + }, + "required": [ + "server", + "tool" + ], + "type": "object" + }, + "McpServerOauthLoginCompletedNotification": { + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "name", + "success" + ], + "type": "object" + }, + "McpStartupFailure": { + "properties": { + "error": { + "type": "string" + }, + "server": { + "type": "string" + } + }, + "required": [ + "error", + "server" + ], + "type": "object" + }, + "McpStartupStatus": { + "oneOf": [ + { + "properties": { + "state": { + "enum": [ + "starting" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "ready" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus2", + "type": "object" + }, + { + "properties": { + "error": { + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "error", + "state" + ], + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "cancelled" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus3", + "type": "object" + } + ] + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallProgressNotification": { + "properties": { + "itemId": { + "type": "string" + }, + "message": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "message", + "threadId", + "turnId" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "PlanDeltaNotification": { + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "type": "object" + }, + "PlanItemArg": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/definitions/StepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitSnapshot2": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot2" + }, + { + "type": "null" + } + ] + }, + "plan_type": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow2" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow2" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resetsAt": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "usedPercent": { + "format": "int32", + "type": "integer" + }, + "windowDurationMins": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "usedPercent" + ], + "type": "object" + }, + "RateLimitWindow2": { + "properties": { + "resets_at": { + "description": "Unix timestamp (seconds since epoch) when the window resets.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "used_percent": { + "description": "Percentage (0-100) of the window that has been consumed.", + "format": "double", + "type": "number" + }, + "window_minutes": { + "description": "Rolling window duration, in minutes.", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "used_percent" + ], + "type": "object" + }, + "RawResponseItemCompletedNotification": { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "ReasoningSummaryPartAddedNotification": { + "properties": { + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "type": "object" + }, + "ReasoningSummaryTextDeltaNotification": { + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "type": "object" + }, + "ReasoningTextDeltaNotification": { + "properties": { + "contentIndex": { + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "contentIndex", + "delta", + "itemId", + "threadId", + "turnId" + ], + "type": "object" + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "RequestUserInputQuestion": { + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestionOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "RequestUserInputQuestionOption": { + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction2" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "Result_of_CallToolResult_or_String": { + "oneOf": [ + { + "properties": { + "Ok": { + "$ref": "#/definitions/CallToolResult" + } + }, + "required": [ + "Ok" + ], + "title": "OkResult_of_CallToolResult_or_String", + "type": "object" + }, + { + "properties": { + "Err": { + "type": "string" + } + }, + "required": [ + "Err" + ], + "title": "ErrResult_of_CallToolResult_or_String", + "type": "object" + } + ] + }, + "ReviewCodeLocation": { + "description": "Location of the code related to a review finding.", + "properties": { + "absolute_file_path": { + "type": "string" + }, + "line_range": { + "$ref": "#/definitions/ReviewLineRange" + } + }, + "required": [ + "absolute_file_path", + "line_range" + ], + "type": "object" + }, + "ReviewFinding": { + "description": "A single review finding describing an observed issue or recommendation.", + "properties": { + "body": { + "type": "string" + }, + "code_location": { + "$ref": "#/definitions/ReviewCodeLocation" + }, + "confidence_score": { + "format": "float", + "type": "number" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "body", + "code_location", + "confidence_score", + "priority", + "title" + ], + "type": "object" + }, + "ReviewLineRange": { + "description": "Inclusive line range in a file associated with the finding.", + "properties": { + "end": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ReviewOutputEvent": { + "description": "Structured review result produced by a child review session.", + "properties": { + "findings": { + "items": { + "$ref": "#/definitions/ReviewFinding" + }, + "type": "array" + }, + "overall_confidence_score": { + "format": "float", + "type": "number" + }, + "overall_correctness": { + "type": "string" + }, + "overall_explanation": { + "type": "string" + } + }, + "required": [ + "findings", + "overall_confidence_score", + "overall_correctness", + "overall_explanation" + ], + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions provided by the user.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SessionConfiguredNotification": { + "properties": { + "historyEntryCount": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "historyLogId": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initialMessages": { + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "rolloutPath": { + "type": "string" + }, + "sessionId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "historyEntryCount", + "historyLogId", + "model", + "rolloutPath", + "sessionId" + ], + "type": "object" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brand_color": { + "type": [ + "string", + "null" + ] + }, + "default_prompt": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "icon_large": { + "type": [ + "string", + "null" + ] + }, + "icon_small": { + "type": [ + "string", + "null" + ] + }, + "short_description": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "short_description": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "StepStatus": { + "enum": [ + "pending", + "in_progress", + "completed" + ], + "type": "string" + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TerminalInteractionNotification": { + "properties": { + "itemId": { + "type": "string" + }, + "processId": { + "type": "string" + }, + "stdin": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "processId", + "stdin", + "threadId", + "turnId" + ], + "type": "object" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextElement2": { + "properties": { + "byte_range": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange2" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byte_range" + ], + "type": "object" + }, + "TextPosition": { + "properties": { + "column": { + "description": "1-based column number (in Unicode scalar values).", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "line": { + "description": "1-based line number.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "column", + "line" + ], + "type": "object" + }, + "TextRange": { + "properties": { + "end": { + "$ref": "#/definitions/TextPosition" + }, + "start": { + "$ref": "#/definitions/TextPosition" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "ThreadNameUpdatedNotification": { + "properties": { + "threadId": { + "type": "string" + }, + "threadName": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, + "ThreadStartedNotification": { + "properties": { + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "thread" + ], + "type": "object" + }, + "ThreadTokenUsage": { + "properties": { + "last": { + "$ref": "#/definitions/TokenUsageBreakdown" + }, + "modelContextWindow": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total": { + "$ref": "#/definitions/TokenUsageBreakdown" + } + }, + "required": [ + "last", + "total" + ], + "type": "object" + }, + "ThreadTokenUsageUpdatedNotification": { + "properties": { + "threadId": { + "type": "string" + }, + "tokenUsage": { + "$ref": "#/definitions/ThreadTokenUsage" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "tokenUsage", + "turnId" + ], + "type": "object" + }, + "TokenUsage": { + "properties": { + "cached_input_tokens": { + "format": "int64", + "type": "integer" + }, + "input_tokens": { + "format": "int64", + "type": "integer" + }, + "output_tokens": { + "format": "int64", + "type": "integer" + }, + "reasoning_output_tokens": { + "format": "int64", + "type": "integer" + }, + "total_tokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cached_input_tokens", + "input_tokens", + "output_tokens", + "reasoning_output_tokens", + "total_tokens" + ], + "type": "object" + }, + "TokenUsageBreakdown": { + "properties": { + "cachedInputTokens": { + "format": "int64", + "type": "integer" + }, + "inputTokens": { + "format": "int64", + "type": "integer" + }, + "outputTokens": { + "format": "int64", + "type": "integer" + }, + "reasoningOutputTokens": { + "format": "int64", + "type": "integer" + }, + "totalTokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cachedInputTokens", + "inputTokens", + "outputTokens", + "reasoningOutputTokens", + "totalTokens" + ], + "type": "object" + }, + "TokenUsageInfo": { + "properties": { + "last_token_usage": { + "$ref": "#/definitions/TokenUsage" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total_token_usage": { + "$ref": "#/definitions/TokenUsage" + } + }, + "required": [ + "last_token_usage", + "total_token_usage" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + }, + "TurnCompletedNotification": { + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "type": "object" + }, + "TurnDiffUpdatedNotification": { + "description": "Notification that the turn-level unified diff has changed. Contains the latest aggregated diff across all file changes in the turn.", + "properties": { + "diff": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "diff", + "threadId", + "turnId" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput2" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "UserMessage" + ], + "title": "UserMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageTurnItem", + "type": "object" + }, + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/AgentMessageContent" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "AgentMessage" + ], + "title": "AgentMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "AgentMessageTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Plan" + ], + "title": "PlanTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "raw_content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "summary_text": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "Reasoning" + ], + "title": "ReasoningTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary_text", + "type" + ], + "title": "ReasoningTurnItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction2" + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "WebSearch" + ], + "title": "WebSearchTurnItemType", + "type": "string" + } + }, + "required": [ + "action", + "id", + "query", + "type" + ], + "title": "WebSearchTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "ContextCompaction" + ], + "title": "ContextCompactionTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionTurnItem", + "type": "object" + } + ] + }, + "TurnPlanStep": { + "properties": { + "status": { + "$ref": "#/definitions/TurnPlanStepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "TurnPlanStepStatus": { + "enum": [ + "pending", + "inProgress", + "completed" + ], + "type": "string" + }, + "TurnPlanUpdatedNotification": { + "properties": { + "explanation": { + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/TurnPlanStep" + }, + "type": "array" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "plan", + "threadId", + "turnId" + ], + "type": "object" + }, + "TurnStartedNotification": { + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "UserInput2": { + "description": "User input", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` that should be treated as special elements. These are byte ranges into the UTF-8 `text` buffer and are used to render or persist rich input markers (e.g., image placeholders) across history and resume without mutating the literal text.", + "items": { + "$ref": "#/definitions/TextElement2" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInput2Type", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput2", + "type": "object" + }, + { + "description": "Pre‑encoded data: URI image.", + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInput2Type", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "ImageUserInput2", + "type": "object" + }, + { + "description": "Local image path provided by the user. This will be converted to an `Image` variant (base64 data URL) during request serialization.", + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "local_image" + ], + "title": "LocalImageUserInput2Type", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput2", + "type": "object" + }, + { + "description": "Skill selected by the user (name + path to SKILL.md).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInput2Type", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput2", + "type": "object" + }, + { + "description": "Explicit mention selected by the user (name + app://connector id).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInput2Type", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput2", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + }, + "WebSearchAction2": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchAction2Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction2", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchAction2Type", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction2", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchAction2Type", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction2", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchAction2Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction2", + "type": "object" + } + ] + }, + "WindowsWorldWritableWarningNotification": { + "properties": { + "extraCount": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "failedScan": { + "type": "boolean" + }, + "samplePaths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "extraCount", + "failedScan", + "samplePaths" + ], + "type": "object" + } + }, + "description": "Notification sent from the server to the client.", + "oneOf": [ + { + "description": "NEW NOTIFICATIONS", + "properties": { + "method": { + "enum": [ + "error" + ], + "title": "ErrorNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ErrorNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ErrorNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/started" + ], + "title": "Thread/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/name/updated" + ], + "title": "Thread/name/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadNameUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/name/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/tokenUsage/updated" + ], + "title": "Thread/tokenUsage/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadTokenUsageUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/tokenUsage/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/started" + ], + "title": "Turn/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/completed" + ], + "title": "Turn/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/diff/updated" + ], + "title": "Turn/diff/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnDiffUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/diff/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/plan/updated" + ], + "title": "Turn/plan/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnPlanUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/plan/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/started" + ], + "title": "Item/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ItemStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/completed" + ], + "title": "Item/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ItemCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/completedNotification", + "type": "object" + }, + { + "description": "This event is internal-only. Used by Codex Cloud.", + "properties": { + "method": { + "enum": [ + "rawResponseItem/completed" + ], + "title": "RawResponseItem/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/RawResponseItemCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "RawResponseItem/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/agentMessage/delta" + ], + "title": "Item/agentMessage/deltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AgentMessageDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/agentMessage/deltaNotification", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items.", + "properties": { + "method": { + "enum": [ + "item/plan/delta" + ], + "title": "Item/plan/deltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/PlanDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/plan/deltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/commandExecution/outputDelta" + ], + "title": "Item/commandExecution/outputDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CommandExecutionOutputDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/commandExecution/outputDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/commandExecution/terminalInteraction" + ], + "title": "Item/commandExecution/terminalInteractionNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TerminalInteractionNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/commandExecution/terminalInteractionNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/fileChange/outputDelta" + ], + "title": "Item/fileChange/outputDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FileChangeOutputDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/fileChange/outputDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/mcpToolCall/progress" + ], + "title": "Item/mcpToolCall/progressNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/McpToolCallProgressNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/mcpToolCall/progressNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "mcpServer/oauthLogin/completed" + ], + "title": "McpServer/oauthLogin/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/McpServerOauthLoginCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "McpServer/oauthLogin/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/updated" + ], + "title": "Account/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AccountUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/rateLimits/updated" + ], + "title": "Account/rateLimits/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AccountRateLimitsUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/rateLimits/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/summaryTextDelta" + ], + "title": "Item/reasoning/summaryTextDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReasoningSummaryTextDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/summaryTextDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/summaryPartAdded" + ], + "title": "Item/reasoning/summaryPartAddedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReasoningSummaryPartAddedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/summaryPartAddedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/textDelta" + ], + "title": "Item/reasoning/textDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReasoningTextDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/textDeltaNotification", + "type": "object" + }, + { + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "properties": { + "method": { + "enum": [ + "thread/compacted" + ], + "title": "Thread/compactedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ContextCompactedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/compactedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "deprecationNotice" + ], + "title": "DeprecationNoticeNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeprecationNoticeNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "DeprecationNoticeNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "configWarning" + ], + "title": "ConfigWarningNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigWarningNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ConfigWarningNotification", + "type": "object" + }, + { + "description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.", + "properties": { + "method": { + "enum": [ + "windows/worldWritableWarning" + ], + "title": "Windows/worldWritableWarningNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/WindowsWorldWritableWarningNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Windows/worldWritableWarningNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/login/completed" + ], + "title": "Account/login/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AccountLoginCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/login/completedNotification", + "type": "object" + }, + { + "description": "DEPRECATED NOTIFICATIONS below", + "properties": { + "method": { + "enum": [ + "authStatusChange" + ], + "title": "AuthStatusChangeNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AuthStatusChangeNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "AuthStatusChangeNotification", + "type": "object" + }, + { + "description": "Deprecated: use `account/login/completed` instead.", + "properties": { + "method": { + "enum": [ + "loginChatGptComplete" + ], + "title": "LoginChatGptCompleteNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoginChatGptCompleteNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "LoginChatGptCompleteNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "sessionConfigured" + ], + "title": "SessionConfiguredNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SessionConfiguredNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "SessionConfiguredNotification", + "type": "object" + } + ], + "title": "ServerNotification" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ServerRequest.json b/codex-rs/app-server-protocol/schema/json/ServerRequest.json new file mode 100644 index 0000000000..ad0c2e3542 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ServerRequest.json @@ -0,0 +1,792 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ApplyPatchApprovalParams": { + "properties": { + "callId": { + "description": "Use to correlate this with [codex_core::protocol::PatchApplyBeginEvent] and [codex_core::protocol::PatchApplyEndEvent].", + "type": "string" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "fileChanges": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grantRoot": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "callId", + "conversationId", + "fileChanges" + ], + "type": "object" + }, + "ChatgptAuthTokensRefreshParams": { + "properties": { + "previousAccountId": { + "description": "Workspace/account identifier that Codex was previously using.\n\nClients that manage multiple accounts/workspaces can use this as a hint to refresh the token for the correct workspace.\n\nThis may be `null` when the prior ID token did not include a workspace identifier (`chatgpt_account_id`) or when the token could not be parsed.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "$ref": "#/definitions/ChatgptAuthTokensRefreshReason" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "ChatgptAuthTokensRefreshReason": { + "oneOf": [ + { + "description": "Codex attempted a backend request and received `401 Unauthorized`.", + "enum": [ + "unauthorized" + ], + "type": "string" + } + ] + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionRequestApprovalParams": { + "properties": { + "command": { + "description": "The command to be executed.", + "type": [ + "string", + "null" + ] + }, + "commandActions": { + "description": "Best-effort parsed command actions for friendly display.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": [ + "array", + "null" + ] + }, + "cwd": { + "description": "The command's working directory.", + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "proposedExecpolicyAmendment": { + "description": "Optional proposed execpolicy amendment to allow similar commands without prompting.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for network access).", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "threadId", + "turnId" + ], + "type": "object" + }, + "DynamicToolCallParams": { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "threadId", + "tool", + "turnId" + ], + "type": "object" + }, + "ExecCommandApprovalParams": { + "properties": { + "callId": { + "description": "Use to correlate this with [codex_core::protocol::ExecCommandBeginEvent] and [codex_core::protocol::ExecCommandEndEvent].", + "type": "string" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "parsedCmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "reason": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "callId", + "command", + "conversationId", + "cwd", + "parsedCmd" + ], + "type": "object" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FileChangeRequestApprovalParams": { + "properties": { + "grantRoot": { + "description": "[UNSTABLE] When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "threadId", + "turnId" + ], + "type": "object" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "ThreadId": { + "type": "string" + }, + "ToolRequestUserInputOption": { + "description": "EXPERIMENTAL. Defines a single selectable option for request_user_input.", + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "ToolRequestUserInputParams": { + "description": "EXPERIMENTAL. Params sent with a request_user_input event.", + "properties": { + "itemId": { + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/ToolRequestUserInputQuestion" + }, + "type": "array" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "questions", + "threadId", + "turnId" + ], + "type": "object" + }, + "ToolRequestUserInputQuestion": { + "description": "EXPERIMENTAL. Represents one request_user_input question and its required options.", + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/ToolRequestUserInputOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + } + }, + "description": "Request initiated from the server and sent to the client.", + "oneOf": [ + { + "description": "NEW APIs Sent when approval is requested for a specific command execution. This request is used for Turns started via turn/start.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/commandExecution/requestApproval" + ], + "title": "Item/commandExecution/requestApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CommandExecutionRequestApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/commandExecution/requestApprovalRequest", + "type": "object" + }, + { + "description": "Sent when approval is requested for a specific file change. This request is used for Turns started via turn/start.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/fileChange/requestApproval" + ], + "title": "Item/fileChange/requestApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FileChangeRequestApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/fileChange/requestApprovalRequest", + "type": "object" + }, + { + "description": "EXPERIMENTAL - Request input from the user for a tool call.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/tool/requestUserInput" + ], + "title": "Item/tool/requestUserInputRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ToolRequestUserInputParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/tool/requestUserInputRequest", + "type": "object" + }, + { + "description": "Execute a dynamic tool call on the client.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/tool/call" + ], + "title": "Item/tool/callRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DynamicToolCallParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/tool/callRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/chatgptAuthTokens/refresh" + ], + "title": "Account/chatgptAuthTokens/refreshRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ChatgptAuthTokensRefreshParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/chatgptAuthTokens/refreshRequest", + "type": "object" + }, + { + "description": "DEPRECATED APIs below Request to approve a patch. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "applyPatchApproval" + ], + "title": "ApplyPatchApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ApplyPatchApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ApplyPatchApprovalRequest", + "type": "object" + }, + { + "description": "Request to exec a command. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "execCommandApproval" + ], + "title": "ExecCommandApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExecCommandApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExecCommandApprovalRequest", + "type": "object" + } + ], + "title": "ServerRequest" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json b/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json new file mode 100644 index 0000000000..153d3bad67 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ToolRequestUserInputOption": { + "description": "EXPERIMENTAL. Defines a single selectable option for request_user_input.", + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "ToolRequestUserInputQuestion": { + "description": "EXPERIMENTAL. Represents one request_user_input question and its required options.", + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/ToolRequestUserInputOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + } + }, + "description": "EXPERIMENTAL. Params sent with a request_user_input event.", + "properties": { + "itemId": { + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/ToolRequestUserInputQuestion" + }, + "type": "array" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "questions", + "threadId", + "turnId" + ], + "title": "ToolRequestUserInputParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputResponse.json b/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputResponse.json new file mode 100644 index 0000000000..3fd6fbc335 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputResponse.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ToolRequestUserInputAnswer": { + "description": "EXPERIMENTAL. Captures a user's answer to a request_user_input question.", + "properties": { + "answers": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "answers" + ], + "type": "object" + } + }, + "description": "EXPERIMENTAL. Response payload mapping question ids to answers.", + "properties": { + "answers": { + "additionalProperties": { + "$ref": "#/definitions/ToolRequestUserInputAnswer" + }, + "type": "object" + } + }, + "required": [ + "answers" + ], + "title": "ToolRequestUserInputResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json new file mode 100644 index 0000000000..413f1348e5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -0,0 +1,16269 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AddConversationListenerParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "experimentalRawEvents": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "conversationId" + ], + "title": "AddConversationListenerParams", + "type": "object" + }, + "AddConversationSubscriptionResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "subscriptionId": { + "type": "string" + } + }, + "required": [ + "subscriptionId" + ], + "title": "AddConversationSubscriptionResponse", + "type": "object" + }, + "AgentMessageContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Text" + ], + "title": "TextAgentMessageContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextAgentMessageContent", + "type": "object" + } + ] + }, + "AgentStatus": { + "description": "Agent lifecycle status, derived from emitted events.", + "oneOf": [ + { + "description": "Agent is waiting for initialization.", + "enum": [ + "pending_init" + ], + "type": "string" + }, + { + "description": "Agent is currently running.", + "enum": [ + "running" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Agent is done. Contains the final assistant message.", + "properties": { + "completed": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "completed" + ], + "title": "CompletedAgentStatus", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Agent encountered an error.", + "properties": { + "errored": { + "type": "string" + } + }, + "required": [ + "errored" + ], + "title": "ErroredAgentStatus", + "type": "object" + }, + { + "description": "Agent has been shutdown.", + "enum": [ + "shutdown" + ], + "type": "string" + }, + { + "description": "Agent is not found.", + "enum": [ + "not_found" + ], + "type": "string" + } + ] + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "ApplyPatchApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "callId": { + "description": "Use to correlate this with [codex_core::protocol::PatchApplyBeginEvent] and [codex_core::protocol::PatchApplyEndEvent].", + "type": "string" + }, + "conversationId": { + "$ref": "#/definitions/v2/ThreadId" + }, + "fileChanges": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grantRoot": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "callId", + "conversationId", + "fileChanges" + ], + "title": "ApplyPatchApprovalParams", + "type": "object" + }, + "ApplyPatchApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "decision": { + "$ref": "#/definitions/ReviewDecision" + } + }, + "required": [ + "decision" + ], + "title": "ApplyPatchApprovalResponse", + "type": "object" + }, + "ArchiveConversationParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "rolloutPath" + ], + "title": "ArchiveConversationParams", + "type": "object" + }, + "ArchiveConversationResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ArchiveConversationResponse", + "type": "object" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "enum": [ + "apikey" + ], + "type": "string" + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "enum": [ + "chatgpt" + ], + "type": "string" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "enum": [ + "chatgptAuthTokens" + ], + "type": "string" + } + ] + }, + "AuthStatusChangeNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Deprecated notification. Use AccountUpdatedNotification instead.", + "properties": { + "authMethod": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + } + }, + "title": "AuthStatusChangeNotification", + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "CancelLoginChatGptParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "loginId": { + "type": "string" + } + }, + "required": [ + "loginId" + ], + "title": "CancelLoginChatGptParams", + "type": "object" + }, + "CancelLoginChatGptResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CancelLoginChatGptResponse", + "type": "object" + }, + "ChatgptAuthTokensRefreshParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "previousAccountId": { + "description": "Workspace/account identifier that Codex was previously using.\n\nClients that manage multiple accounts/workspaces can use this as a hint to refresh the token for the correct workspace.\n\nThis may be `null` when the prior ID token did not include a workspace identifier (`chatgpt_account_id`) or when the token could not be parsed.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "$ref": "#/definitions/ChatgptAuthTokensRefreshReason" + } + }, + "required": [ + "reason" + ], + "title": "ChatgptAuthTokensRefreshParams", + "type": "object" + }, + "ChatgptAuthTokensRefreshReason": { + "oneOf": [ + { + "description": "Codex attempted a backend request and received `401 Unauthorized`.", + "enum": [ + "unauthorized" + ], + "type": "string" + } + ] + }, + "ChatgptAuthTokensRefreshResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "accessToken": { + "type": "string" + }, + "idToken": { + "type": "string" + } + }, + "required": [ + "accessToken", + "idToken" + ], + "title": "ChatgptAuthTokensRefreshResponse", + "type": "object" + }, + "ClientInfo": { + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "ClientNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "method": { + "enum": [ + "initialized" + ], + "title": "InitializedNotificationMethod", + "type": "string" + } + }, + "required": [ + "method" + ], + "title": "InitializedNotification", + "type": "object" + } + ], + "title": "ClientNotification" + }, + "ClientRequest": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Request from the client to the server.", + "oneOf": [ + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "initialize" + ], + "title": "InitializeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/InitializeParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "InitializeRequest", + "type": "object" + }, + { + "description": "NEW APIs", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/start" + ], + "title": "Thread/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/resume" + ], + "title": "Thread/resumeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadResumeParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/resumeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/fork" + ], + "title": "Thread/forkRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadForkParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/forkRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/archive" + ], + "title": "Thread/archiveRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadArchiveParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/archiveRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/name/set" + ], + "title": "Thread/name/setRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadSetNameParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/name/setRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/unarchive" + ], + "title": "Thread/unarchiveRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadUnarchiveParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/unarchiveRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/rollback" + ], + "title": "Thread/rollbackRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRollbackParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/rollbackRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/list" + ], + "title": "Thread/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/loaded/list" + ], + "title": "Thread/loaded/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadLoadedListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/loaded/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/read" + ], + "title": "Thread/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/list" + ], + "title": "Skills/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/SkillsListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "app/list" + ], + "title": "App/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/AppsListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "App/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/config/write" + ], + "title": "Skills/config/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/SkillsConfigWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/config/writeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/start" + ], + "title": "Turn/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TurnStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/interrupt" + ], + "title": "Turn/interruptRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TurnInterruptParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/interruptRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "review/start" + ], + "title": "Review/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ReviewStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Review/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "model/list" + ], + "title": "Model/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ModelListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Model/listRequest", + "type": "object" + }, + { + "description": "EXPERIMENTAL - list collaboration mode presets.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "collaborationMode/list" + ], + "title": "CollaborationMode/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/CollaborationModeListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "CollaborationMode/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "mcpServer/oauth/login" + ], + "title": "McpServer/oauth/loginRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/McpServerOauthLoginParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "McpServer/oauth/loginRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/mcpServer/reload" + ], + "title": "Config/mcpServer/reloadRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Config/mcpServer/reloadRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "mcpServerStatus/list" + ], + "title": "McpServerStatus/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ListMcpServerStatusParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "McpServerStatus/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/login/start" + ], + "title": "Account/login/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/LoginAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/login/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/login/cancel" + ], + "title": "Account/login/cancelRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/CancelLoginAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/login/cancelRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/logout" + ], + "title": "Account/logoutRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Account/logoutRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/rateLimits/read" + ], + "title": "Account/rateLimits/readRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Account/rateLimits/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "feedback/upload" + ], + "title": "Feedback/uploadRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/FeedbackUploadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Feedback/uploadRequest", + "type": "object" + }, + { + "description": "Execute a command (argv vector) under the server's sandbox.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "command/exec" + ], + "title": "Command/execRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Command/execRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/read" + ], + "title": "Config/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ConfigReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/value/write" + ], + "title": "Config/value/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ConfigValueWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/value/writeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/batchWrite" + ], + "title": "Config/batchWriteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ConfigBatchWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/batchWriteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "configRequirements/read" + ], + "title": "ConfigRequirements/readRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "ConfigRequirements/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/read" + ], + "title": "Account/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/GetAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/readRequest", + "type": "object" + }, + { + "description": "DEPRECATED APIs below", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "newConversation" + ], + "title": "NewConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/NewConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "NewConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getConversationSummary" + ], + "title": "GetConversationSummaryRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GetConversationSummaryParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "GetConversationSummaryRequest", + "type": "object" + }, + { + "description": "List recorded Codex conversations (rollouts) with optional pagination and search.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "listConversations" + ], + "title": "ListConversationsRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ListConversationsParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ListConversationsRequest", + "type": "object" + }, + { + "description": "Resume a recorded Codex conversation from a rollout file.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "resumeConversation" + ], + "title": "ResumeConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ResumeConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ResumeConversationRequest", + "type": "object" + }, + { + "description": "Fork a recorded Codex conversation into a new session.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "forkConversation" + ], + "title": "ForkConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ForkConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ForkConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "archiveConversation" + ], + "title": "ArchiveConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ArchiveConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ArchiveConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "sendUserMessage" + ], + "title": "SendUserMessageRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SendUserMessageParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "SendUserMessageRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "sendUserTurn" + ], + "title": "SendUserTurnRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SendUserTurnParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "SendUserTurnRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "interruptConversation" + ], + "title": "InterruptConversationRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/InterruptConversationParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "InterruptConversationRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "addConversationListener" + ], + "title": "AddConversationListenerRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AddConversationListenerParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "AddConversationListenerRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "removeConversationListener" + ], + "title": "RemoveConversationListenerRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/RemoveConversationListenerParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "RemoveConversationListenerRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "gitDiffToRemote" + ], + "title": "GitDiffToRemoteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GitDiffToRemoteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "GitDiffToRemoteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "loginApiKey" + ], + "title": "LoginApiKeyRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoginApiKeyParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "LoginApiKeyRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "loginChatGpt" + ], + "title": "LoginChatGptRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "LoginChatGptRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "cancelLoginChatGpt" + ], + "title": "CancelLoginChatGptRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CancelLoginChatGptParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "CancelLoginChatGptRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "logoutChatGpt" + ], + "title": "LogoutChatGptRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "LogoutChatGptRequest", + "type": "object" + }, + { + "description": "DEPRECATED in favor of GetAccount", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getAuthStatus" + ], + "title": "GetAuthStatusRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GetAuthStatusParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "GetAuthStatusRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getUserSavedConfig" + ], + "title": "GetUserSavedConfigRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "GetUserSavedConfigRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "setDefaultModel" + ], + "title": "SetDefaultModelRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SetDefaultModelParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "SetDefaultModelRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "getUserAgent" + ], + "title": "GetUserAgentRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "GetUserAgentRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "userInfo" + ], + "title": "UserInfoRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "UserInfoRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "fuzzyFileSearch" + ], + "title": "FuzzyFileSearchRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "FuzzyFileSearchRequest", + "type": "object" + }, + { + "description": "Execute a command (argv vector) under the server's sandbox.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "execOneOffCommand" + ], + "title": "ExecOneOffCommandRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExecOneOffCommandParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExecOneOffCommandRequest", + "type": "object" + } + ], + "title": "ClientRequest" + }, + "CodexErrorInfo": { + "description": "Codex errors that we expose to clients.", + "oneOf": [ + { + "enum": [ + "context_window_exceeded", + "usage_limit_exceeded", + "internal_server_error", + "unauthorized", + "bad_request", + "sandbox_error", + "thread_rollback_failed", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "model_cap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "model_cap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "http_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "http_connection_failed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "response_stream_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_connection_failed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turnbefore completion.", + "properties": { + "response_stream_disconnected": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_disconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "response_too_many_failed_attempts": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_too_many_failed_attempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CommandExecutionApprovalDecision": { + "oneOf": [ + { + "description": "User approved the command.", + "enum": [ + "accept" + ], + "type": "string" + }, + { + "description": "User approved the command and future identical commands should run without prompting.", + "enum": [ + "acceptForSession" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "User approved the command, and wants to apply the proposed execpolicy amendment so future matching commands can run without prompting.", + "properties": { + "acceptWithExecpolicyAmendment": { + "properties": { + "execpolicy_amendment": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "execpolicy_amendment" + ], + "type": "object" + } + }, + "required": [ + "acceptWithExecpolicyAmendment" + ], + "title": "AcceptWithExecpolicyAmendmentCommandExecutionApprovalDecision", + "type": "object" + }, + { + "description": "User denied the command. The agent will continue the turn.", + "enum": [ + "decline" + ], + "type": "string" + }, + { + "description": "User denied the command. The turn will also be immediately interrupted.", + "enum": [ + "cancel" + ], + "type": "string" + } + ] + }, + "CommandExecutionRequestApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "command": { + "description": "The command to be executed.", + "type": [ + "string", + "null" + ] + }, + "commandActions": { + "description": "Best-effort parsed command actions for friendly display.", + "items": { + "$ref": "#/definitions/v2/CommandAction" + }, + "type": [ + "array", + "null" + ] + }, + "cwd": { + "description": "The command's working directory.", + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "proposedExecpolicyAmendment": { + "description": "Optional proposed execpolicy amendment to allow similar commands without prompting.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for network access).", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "threadId", + "turnId" + ], + "title": "CommandExecutionRequestApprovalParams", + "type": "object" + }, + "CommandExecutionRequestApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "decision": { + "$ref": "#/definitions/CommandExecutionApprovalDecision" + } + }, + "required": [ + "decision" + ], + "title": "CommandExecutionRequestApprovalResponse", + "type": "object" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "ConversationGitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "origin_url": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ConversationSummary": { + "properties": { + "cliVersion": { + "type": "string" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/ConversationGitInfo" + }, + { + "type": "null" + } + ] + }, + "modelProvider": { + "type": "string" + }, + "path": { + "type": "string" + }, + "preview": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/SessionSource" + }, + "timestamp": { + "type": [ + "string", + "null" + ] + }, + "updatedAt": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "cliVersion", + "conversationId", + "cwd", + "modelProvider", + "path", + "preview", + "source" + ], + "type": "object" + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "has_credits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "has_credits", + "unlimited" + ], + "type": "object" + }, + "CustomPrompt": { + "properties": { + "argument_hint": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "Duration": { + "properties": { + "nanos": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "secs": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "nanos", + "secs" + ], + "type": "object" + }, + "DynamicToolCallParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "threadId", + "tool", + "turnId" + ], + "title": "DynamicToolCallParams", + "type": "object" + }, + "DynamicToolCallResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "output": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "output", + "success" + ], + "title": "DynamicToolCallResponse", + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "EventMsg": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/v2/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/v2/ModeKind" + } + ], + "default": "code" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/v2/RateLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/v2/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/v2/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/v2/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/v2/WebSearchAction" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the associated exec call, if available.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/v2/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/v2/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/v2/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/v2/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/v2/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/v2/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/v2/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/v2/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/v2/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/v2/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/v2/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + } + ], + "title": "EventMsg" + }, + "ExecCommandApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "callId": { + "description": "Use to correlate this with [codex_core::protocol::ExecCommandBeginEvent] and [codex_core::protocol::ExecCommandEndEvent].", + "type": "string" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "conversationId": { + "$ref": "#/definitions/v2/ThreadId" + }, + "cwd": { + "type": "string" + }, + "parsedCmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "reason": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "callId", + "command", + "conversationId", + "cwd", + "parsedCmd" + ], + "title": "ExecCommandApprovalParams", + "type": "object" + }, + "ExecCommandApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "decision": { + "$ref": "#/definitions/ReviewDecision" + } + }, + "required": [ + "decision" + ], + "title": "ExecCommandApprovalResponse", + "type": "object" + }, + "ExecCommandSource": { + "enum": [ + "agent", + "user_shell", + "unified_exec_startup", + "unified_exec_interaction" + ], + "type": "string" + }, + "ExecOneOffCommandParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "timeoutMs": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "command" + ], + "title": "ExecOneOffCommandParams", + "type": "object" + }, + "ExecOneOffCommandResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "exitCode": { + "format": "int32", + "type": "integer" + }, + "stderr": { + "type": "string" + }, + "stdout": { + "type": "string" + } + }, + "required": [ + "exitCode", + "stderr", + "stdout" + ], + "title": "ExecOneOffCommandResponse", + "type": "object" + }, + "ExecOutputStream": { + "enum": [ + "stdout", + "stderr" + ], + "type": "string" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FileChangeApprovalDecision": { + "oneOf": [ + { + "description": "User approved the file changes.", + "enum": [ + "accept" + ], + "type": "string" + }, + { + "description": "User approved the file changes and future changes to the same files should run without prompting.", + "enum": [ + "acceptForSession" + ], + "type": "string" + }, + { + "description": "User denied the file changes. The agent will continue the turn.", + "enum": [ + "decline" + ], + "type": "string" + }, + { + "description": "User denied the file changes. The turn will also be immediately interrupted.", + "enum": [ + "cancel" + ], + "type": "string" + } + ] + }, + "FileChangeRequestApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "grantRoot": { + "description": "[UNSTABLE] When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "threadId", + "turnId" + ], + "title": "FileChangeRequestApprovalParams", + "type": "object" + }, + "FileChangeRequestApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "decision": { + "$ref": "#/definitions/FileChangeApprovalDecision" + } + }, + "required": [ + "decision" + ], + "title": "FileChangeRequestApprovalResponse", + "type": "object" + }, + "ForcedLoginMethod": { + "enum": [ + "chatgpt", + "api" + ], + "type": "string" + }, + "ForkConversationParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "overrides": { + "anyOf": [ + { + "$ref": "#/definitions/NewConversationParams" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": [ + "string", + "null" + ] + } + }, + "title": "ForkConversationParams", + "type": "object" + }, + "ForkConversationResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "initialMessages": { + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "type": "string" + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "model", + "rolloutPath" + ], + "title": "ForkConversationResponse", + "type": "object" + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "FuzzyFileSearchParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cancellationToken": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": "string" + }, + "roots": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "query", + "roots" + ], + "title": "FuzzyFileSearchParams", + "type": "object" + }, + "FuzzyFileSearchResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "files": { + "items": { + "$ref": "#/definitions/FuzzyFileSearchResult" + }, + "type": "array" + } + }, + "required": [ + "files" + ], + "title": "FuzzyFileSearchResponse", + "type": "object" + }, + "FuzzyFileSearchResult": { + "description": "Superset of [`codex_file_search::FileMatch`]", + "properties": { + "file_name": { + "type": "string" + }, + "indices": { + "items": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": [ + "array", + "null" + ] + }, + "path": { + "type": "string" + }, + "root": { + "type": "string" + }, + "score": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "file_name", + "path", + "root", + "score" + ], + "type": "object" + }, + "GetAuthStatusParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "includeToken": { + "type": [ + "boolean", + "null" + ] + }, + "refreshToken": { + "type": [ + "boolean", + "null" + ] + } + }, + "title": "GetAuthStatusParams", + "type": "object" + }, + "GetAuthStatusResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authMethod": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + }, + "authToken": { + "type": [ + "string", + "null" + ] + }, + "requiresOpenaiAuth": { + "type": [ + "boolean", + "null" + ] + } + }, + "title": "GetAuthStatusResponse", + "type": "object" + }, + "GetConversationSummaryParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "anyOf": [ + { + "properties": { + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "rolloutPath" + ], + "title": "RolloutPathv1::GetConversationSummaryParams", + "type": "object" + }, + { + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "conversationId" + ], + "title": "ConversationIdv1::GetConversationSummaryParams", + "type": "object" + } + ], + "title": "GetConversationSummaryParams" + }, + "GetConversationSummaryResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "summary": { + "$ref": "#/definitions/ConversationSummary" + } + }, + "required": [ + "summary" + ], + "title": "GetConversationSummaryResponse", + "type": "object" + }, + "GetUserAgentResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "userAgent": { + "type": "string" + } + }, + "required": [ + "userAgent" + ], + "title": "GetUserAgentResponse", + "type": "object" + }, + "GetUserSavedConfigResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "config": { + "$ref": "#/definitions/UserSavedConfig" + } + }, + "required": [ + "config" + ], + "title": "GetUserSavedConfigResponse", + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "GitDiffToRemoteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwd": { + "type": "string" + } + }, + "required": [ + "cwd" + ], + "title": "GitDiffToRemoteParams", + "type": "object" + }, + "GitDiffToRemoteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "diff": { + "type": "string" + }, + "sha": { + "$ref": "#/definitions/GitSha" + } + }, + "required": [ + "diff", + "sha" + ], + "title": "GitDiffToRemoteResponse", + "type": "object" + }, + "GitSha": { + "type": "string" + }, + "HistoryEntry": { + "properties": { + "conversation_id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "ts": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "conversation_id", + "text", + "ts" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "InitializeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "clientInfo": { + "$ref": "#/definitions/ClientInfo" + } + }, + "required": [ + "clientInfo" + ], + "title": "InitializeParams", + "type": "object" + }, + "InitializeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "userAgent": { + "type": "string" + } + }, + "required": [ + "userAgent" + ], + "title": "InitializeResponse", + "type": "object" + }, + "InputItem": { + "oneOf": [ + { + "properties": { + "data": { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/V1TextElement" + }, + "type": "array" + } + }, + "required": [ + "text" + ], + "type": "object" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "TextInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "image_url": { + "type": "string" + } + }, + "required": [ + "image_url" + ], + "type": "object" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "ImageInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "LocalImageInputItem", + "type": "object" + } + ] + }, + "InterruptConversationParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "conversationId" + ], + "title": "InterruptConversationParams", + "type": "object" + }, + "InterruptConversationResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "abortReason": { + "$ref": "#/definitions/TurnAbortReason" + } + }, + "required": [ + "abortReason" + ], + "title": "InterruptConversationResponse", + "type": "object" + }, + "JSONRPCError": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A response to a request that indicates an error occurred.", + "properties": { + "error": { + "$ref": "#/definitions/JSONRPCErrorError" + }, + "id": { + "$ref": "#/definitions/RequestId" + } + }, + "required": [ + "error", + "id" + ], + "title": "JSONRPCError", + "type": "object" + }, + "JSONRPCErrorError": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "code": { + "format": "int64", + "type": "integer" + }, + "data": true, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "title": "JSONRPCErrorError", + "type": "object" + }, + "JSONRPCMessage": { + "$schema": "http://json-schema.org/draft-07/schema#", + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCRequest" + }, + { + "$ref": "#/definitions/JSONRPCNotification" + }, + { + "$ref": "#/definitions/JSONRPCResponse" + }, + { + "$ref": "#/definitions/JSONRPCError" + } + ], + "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.", + "title": "JSONRPCMessage" + }, + "JSONRPCNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A notification which does not expect a response.", + "properties": { + "method": { + "type": "string" + }, + "params": true + }, + "required": [ + "method" + ], + "title": "JSONRPCNotification", + "type": "object" + }, + "JSONRPCRequest": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A request that expects a response.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "type": "string" + }, + "params": true + }, + "required": [ + "id", + "method" + ], + "title": "JSONRPCRequest", + "type": "object" + }, + "JSONRPCResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A successful (non-error) response to a request.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "result": true + }, + "required": [ + "id", + "result" + ], + "title": "JSONRPCResponse", + "type": "object" + }, + "ListConversationsParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "type": [ + "string", + "null" + ] + }, + "modelProviders": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "pageSize": { + "format": "uint", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ListConversationsParams", + "type": "object" + }, + "ListConversationsResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "items": { + "items": { + "$ref": "#/definitions/ConversationSummary" + }, + "type": "array" + }, + "nextCursor": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "items" + ], + "title": "ListConversationsResponse", + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "LoginApiKeyParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "apiKey": { + "type": "string" + } + }, + "required": [ + "apiKey" + ], + "title": "LoginApiKeyParams", + "type": "object" + }, + "LoginApiKeyResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LoginApiKeyResponse", + "type": "object" + }, + "LoginChatGptCompleteNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Deprecated in favor of AccountLoginCompletedNotification.", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "loginId", + "success" + ], + "title": "LoginChatGptCompleteNotification", + "type": "object" + }, + "LoginChatGptResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authUrl": { + "type": "string" + }, + "loginId": { + "type": "string" + } + }, + "required": [ + "authUrl", + "loginId" + ], + "title": "LoginChatGptResponse", + "type": "object" + }, + "LogoutChatGptResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LogoutChatGptResponse", + "type": "object" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "not_logged_in", + "bearer_token", + "o_auth" + ], + "type": "string" + }, + "McpInvocation": { + "properties": { + "arguments": { + "description": "Arguments to the tool call." + }, + "server": { + "description": "Name of the MCP server as defined in the config.", + "type": "string" + }, + "tool": { + "description": "Name of the tool as given by the MCP server.", + "type": "string" + } + }, + "required": [ + "server", + "tool" + ], + "type": "object" + }, + "McpStartupFailure": { + "properties": { + "error": { + "type": "string" + }, + "server": { + "type": "string" + } + }, + "required": [ + "error", + "server" + ], + "type": "object" + }, + "McpStartupStatus": { + "oneOf": [ + { + "properties": { + "state": { + "enum": [ + "starting" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "ready" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus2", + "type": "object" + }, + { + "properties": { + "error": { + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "error", + "state" + ], + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "cancelled" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus3", + "type": "object" + } + ] + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "NewConversationParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "compactPrompt": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "includeApplyPatchTool": { + "type": [ + "boolean", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "NewConversationResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "model": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "model", + "rolloutPath" + ], + "title": "NewConversationResponse", + "type": "object" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "PlanItemArg": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/definitions/StepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "Profile": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "chatgptBaseUrl": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "modelReasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "modelReasoningSummary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "modelVerbosity": { + "anyOf": [ + { + "$ref": "#/definitions/Verbosity" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "plan_type": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resets_at": { + "description": "Unix timestamp (seconds since epoch) when the window resets.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "used_percent": { + "description": "Percentage (0-100) of the window that has been consumed.", + "format": "double", + "type": "number" + }, + "window_minutes": { + "description": "Rolling window duration, in minutes.", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "used_percent" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "RemoveConversationListenerParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "subscriptionId": { + "type": "string" + } + }, + "required": [ + "subscriptionId" + ], + "title": "RemoveConversationListenerParams", + "type": "object" + }, + "RemoveConversationSubscriptionResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RemoveConversationSubscriptionResponse", + "type": "object" + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "RequestUserInputQuestion": { + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestionOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "RequestUserInputQuestionOption": { + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "Result_of_CallToolResult_or_String": { + "oneOf": [ + { + "properties": { + "Ok": { + "$ref": "#/definitions/CallToolResult" + } + }, + "required": [ + "Ok" + ], + "title": "OkResult_of_CallToolResult_or_String", + "type": "object" + }, + { + "properties": { + "Err": { + "type": "string" + } + }, + "required": [ + "Err" + ], + "title": "ErrResult_of_CallToolResult_or_String", + "type": "object" + } + ] + }, + "ResumeConversationParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history": { + "items": { + "$ref": "#/definitions/ResponseItem" + }, + "type": [ + "array", + "null" + ] + }, + "overrides": { + "anyOf": [ + { + "$ref": "#/definitions/NewConversationParams" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": [ + "string", + "null" + ] + } + }, + "title": "ResumeConversationParams", + "type": "object" + }, + "ResumeConversationResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "initialMessages": { + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "type": "string" + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "model", + "rolloutPath" + ], + "title": "ResumeConversationResponse", + "type": "object" + }, + "ReviewCodeLocation": { + "description": "Location of the code related to a review finding.", + "properties": { + "absolute_file_path": { + "type": "string" + }, + "line_range": { + "$ref": "#/definitions/ReviewLineRange" + } + }, + "required": [ + "absolute_file_path", + "line_range" + ], + "type": "object" + }, + "ReviewDecision": { + "description": "User's decision in response to an ExecApprovalRequest.", + "oneOf": [ + { + "description": "User has approved this command and the agent should execute it.", + "enum": [ + "approved" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.", + "properties": { + "approved_execpolicy_amendment": { + "properties": { + "proposed_execpolicy_amendment": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "proposed_execpolicy_amendment" + ], + "type": "object" + } + }, + "required": [ + "approved_execpolicy_amendment" + ], + "title": "ApprovedExecpolicyAmendmentReviewDecision", + "type": "object" + }, + { + "description": "User has approved this command and wants to automatically approve any future identical instances (`command` and `cwd` match exactly) for the remainder of the session.", + "enum": [ + "approved_for_session" + ], + "type": "string" + }, + { + "description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.", + "enum": [ + "denied" + ], + "type": "string" + }, + { + "description": "User has denied this command and the agent should not do anything until the user's next command.", + "enum": [ + "abort" + ], + "type": "string" + } + ] + }, + "ReviewFinding": { + "description": "A single review finding describing an observed issue or recommendation.", + "properties": { + "body": { + "type": "string" + }, + "code_location": { + "$ref": "#/definitions/ReviewCodeLocation" + }, + "confidence_score": { + "format": "float", + "type": "number" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "body", + "code_location", + "confidence_score", + "priority", + "title" + ], + "type": "object" + }, + "ReviewLineRange": { + "description": "Inclusive line range in a file associated with the finding.", + "properties": { + "end": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ReviewOutputEvent": { + "description": "Structured review result produced by a child review session.", + "properties": { + "findings": { + "items": { + "$ref": "#/definitions/ReviewFinding" + }, + "type": "array" + }, + "overall_confidence_score": { + "format": "float", + "type": "number" + }, + "overall_correctness": { + "type": "string" + }, + "overall_explanation": { + "type": "string" + } + }, + "required": [ + "findings", + "overall_confidence_score", + "overall_correctness", + "overall_explanation" + ], + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions provided by the user.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SandboxSettings": { + "properties": { + "excludeSlashTmp": { + "type": [ + "boolean", + "null" + ] + }, + "excludeTmpdirEnvVar": { + "type": [ + "boolean", + "null" + ] + }, + "networkAccess": { + "type": [ + "boolean", + "null" + ] + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "type": "object" + }, + "SendUserMessageParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "items": { + "items": { + "$ref": "#/definitions/InputItem" + }, + "type": "array" + } + }, + "required": [ + "conversationId", + "items" + ], + "title": "SendUserMessageParams", + "type": "object" + }, + "SendUserMessageResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SendUserMessageResponse", + "type": "object" + }, + "SendUserTurnParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "items": { + "items": { + "$ref": "#/definitions/InputItem" + }, + "type": "array" + }, + "model": { + "type": "string" + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "sandboxPolicy": { + "$ref": "#/definitions/SandboxPolicy" + }, + "summary": { + "$ref": "#/definitions/ReasoningSummary" + } + }, + "required": [ + "approvalPolicy", + "conversationId", + "cwd", + "items", + "model", + "sandboxPolicy", + "summary" + ], + "title": "SendUserTurnParams", + "type": "object" + }, + "SendUserTurnResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SendUserTurnResponse", + "type": "object" + }, + "ServerNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Notification sent from the server to the client.", + "oneOf": [ + { + "description": "NEW NOTIFICATIONS", + "properties": { + "method": { + "enum": [ + "error" + ], + "title": "ErrorNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ErrorNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ErrorNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/started" + ], + "title": "Thread/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/name/updated" + ], + "title": "Thread/name/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadNameUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/name/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/tokenUsage/updated" + ], + "title": "Thread/tokenUsage/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadTokenUsageUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/tokenUsage/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/started" + ], + "title": "Turn/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TurnStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/completed" + ], + "title": "Turn/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TurnCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/diff/updated" + ], + "title": "Turn/diff/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TurnDiffUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/diff/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/plan/updated" + ], + "title": "Turn/plan/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TurnPlanUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/plan/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/started" + ], + "title": "Item/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ItemStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/completed" + ], + "title": "Item/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ItemCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/completedNotification", + "type": "object" + }, + { + "description": "This event is internal-only. Used by Codex Cloud.", + "properties": { + "method": { + "enum": [ + "rawResponseItem/completed" + ], + "title": "RawResponseItem/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/RawResponseItemCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "RawResponseItem/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/agentMessage/delta" + ], + "title": "Item/agentMessage/deltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/AgentMessageDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/agentMessage/deltaNotification", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items.", + "properties": { + "method": { + "enum": [ + "item/plan/delta" + ], + "title": "Item/plan/deltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/PlanDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/plan/deltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/commandExecution/outputDelta" + ], + "title": "Item/commandExecution/outputDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecutionOutputDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/commandExecution/outputDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/commandExecution/terminalInteraction" + ], + "title": "Item/commandExecution/terminalInteractionNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TerminalInteractionNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/commandExecution/terminalInteractionNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/fileChange/outputDelta" + ], + "title": "Item/fileChange/outputDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/FileChangeOutputDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/fileChange/outputDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/mcpToolCall/progress" + ], + "title": "Item/mcpToolCall/progressNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/McpToolCallProgressNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/mcpToolCall/progressNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "mcpServer/oauthLogin/completed" + ], + "title": "McpServer/oauthLogin/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/McpServerOauthLoginCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "McpServer/oauthLogin/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/updated" + ], + "title": "Account/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/AccountUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/rateLimits/updated" + ], + "title": "Account/rateLimits/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/AccountRateLimitsUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/rateLimits/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/summaryTextDelta" + ], + "title": "Item/reasoning/summaryTextDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ReasoningSummaryTextDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/summaryTextDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/summaryPartAdded" + ], + "title": "Item/reasoning/summaryPartAddedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ReasoningSummaryPartAddedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/summaryPartAddedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/textDelta" + ], + "title": "Item/reasoning/textDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ReasoningTextDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/textDeltaNotification", + "type": "object" + }, + { + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "properties": { + "method": { + "enum": [ + "thread/compacted" + ], + "title": "Thread/compactedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ContextCompactedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/compactedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "deprecationNotice" + ], + "title": "DeprecationNoticeNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/DeprecationNoticeNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "DeprecationNoticeNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "configWarning" + ], + "title": "ConfigWarningNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ConfigWarningNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ConfigWarningNotification", + "type": "object" + }, + { + "description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.", + "properties": { + "method": { + "enum": [ + "windows/worldWritableWarning" + ], + "title": "Windows/worldWritableWarningNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/WindowsWorldWritableWarningNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Windows/worldWritableWarningNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/login/completed" + ], + "title": "Account/login/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/AccountLoginCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/login/completedNotification", + "type": "object" + }, + { + "description": "DEPRECATED NOTIFICATIONS below", + "properties": { + "method": { + "enum": [ + "authStatusChange" + ], + "title": "AuthStatusChangeNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AuthStatusChangeNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "AuthStatusChangeNotification", + "type": "object" + }, + { + "description": "Deprecated: use `account/login/completed` instead.", + "properties": { + "method": { + "enum": [ + "loginChatGptComplete" + ], + "title": "LoginChatGptCompleteNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoginChatGptCompleteNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "LoginChatGptCompleteNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "sessionConfigured" + ], + "title": "SessionConfiguredNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SessionConfiguredNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "SessionConfiguredNotification", + "type": "object" + } + ], + "title": "ServerNotification" + }, + "ServerRequest": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Request initiated from the server and sent to the client.", + "oneOf": [ + { + "description": "NEW APIs Sent when approval is requested for a specific command execution. This request is used for Turns started via turn/start.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/commandExecution/requestApproval" + ], + "title": "Item/commandExecution/requestApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CommandExecutionRequestApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/commandExecution/requestApprovalRequest", + "type": "object" + }, + { + "description": "Sent when approval is requested for a specific file change. This request is used for Turns started via turn/start.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/fileChange/requestApproval" + ], + "title": "Item/fileChange/requestApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FileChangeRequestApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/fileChange/requestApprovalRequest", + "type": "object" + }, + { + "description": "EXPERIMENTAL - Request input from the user for a tool call.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/tool/requestUserInput" + ], + "title": "Item/tool/requestUserInputRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ToolRequestUserInputParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/tool/requestUserInputRequest", + "type": "object" + }, + { + "description": "Execute a dynamic tool call on the client.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "item/tool/call" + ], + "title": "Item/tool/callRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DynamicToolCallParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Item/tool/callRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/chatgptAuthTokens/refresh" + ], + "title": "Account/chatgptAuthTokens/refreshRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ChatgptAuthTokensRefreshParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/chatgptAuthTokens/refreshRequest", + "type": "object" + }, + { + "description": "DEPRECATED APIs below Request to approve a patch. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "applyPatchApproval" + ], + "title": "ApplyPatchApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ApplyPatchApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ApplyPatchApprovalRequest", + "type": "object" + }, + { + "description": "Request to exec a command. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "execCommandApproval" + ], + "title": "ExecCommandApprovalRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExecCommandApprovalParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExecCommandApprovalRequest", + "type": "object" + } + ], + "title": "ServerRequest" + }, + "SessionConfiguredNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "historyEntryCount": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "historyLogId": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initialMessages": { + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "rolloutPath": { + "type": "string" + }, + "sessionId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "historyEntryCount", + "historyLogId", + "model", + "rolloutPath", + "sessionId" + ], + "title": "SessionConfiguredNotification", + "type": "object" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "mcp", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subagent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subagent" + ], + "title": "SubagentSessionSource", + "type": "object" + } + ] + }, + "SetDefaultModelParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "model": { + "type": [ + "string", + "null" + ] + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "title": "SetDefaultModelParams", + "type": "object" + }, + "SetDefaultModelResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SetDefaultModelResponse", + "type": "object" + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brand_color": { + "type": [ + "string", + "null" + ] + }, + "default_prompt": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "icon_large": { + "type": [ + "string", + "null" + ] + }, + "icon_small": { + "type": [ + "string", + "null" + ] + }, + "short_description": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "short_description": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "StepStatus": { + "enum": [ + "pending", + "in_progress", + "completed" + ], + "type": "string" + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byte_range": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byte_range" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "TokenUsage": { + "properties": { + "cached_input_tokens": { + "format": "int64", + "type": "integer" + }, + "input_tokens": { + "format": "int64", + "type": "integer" + }, + "output_tokens": { + "format": "int64", + "type": "integer" + }, + "reasoning_output_tokens": { + "format": "int64", + "type": "integer" + }, + "total_tokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cached_input_tokens", + "input_tokens", + "output_tokens", + "reasoning_output_tokens", + "total_tokens" + ], + "type": "object" + }, + "TokenUsageInfo": { + "properties": { + "last_token_usage": { + "$ref": "#/definitions/TokenUsage" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total_token_usage": { + "$ref": "#/definitions/TokenUsage" + } + }, + "required": [ + "last_token_usage", + "total_token_usage" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolRequestUserInputAnswer": { + "description": "EXPERIMENTAL. Captures a user's answer to a request_user_input question.", + "properties": { + "answers": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "answers" + ], + "type": "object" + }, + "ToolRequestUserInputOption": { + "description": "EXPERIMENTAL. Defines a single selectable option for request_user_input.", + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "ToolRequestUserInputParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL. Params sent with a request_user_input event.", + "properties": { + "itemId": { + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/ToolRequestUserInputQuestion" + }, + "type": "array" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "questions", + "threadId", + "turnId" + ], + "title": "ToolRequestUserInputParams", + "type": "object" + }, + "ToolRequestUserInputQuestion": { + "description": "EXPERIMENTAL. Represents one request_user_input question and its required options.", + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/ToolRequestUserInputOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "ToolRequestUserInputResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL. Response payload mapping question ids to answers.", + "properties": { + "answers": { + "additionalProperties": { + "$ref": "#/definitions/ToolRequestUserInputAnswer" + }, + "type": "object" + } + }, + "required": [ + "answers" + ], + "title": "ToolRequestUserInputResponse", + "type": "object" + }, + "Tools": { + "properties": { + "viewImage": { + "type": [ + "boolean", + "null" + ] + }, + "webSearch": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + }, + "TurnItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "UserMessage" + ], + "title": "UserMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageTurnItem", + "type": "object" + }, + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/AgentMessageContent" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "AgentMessage" + ], + "title": "AgentMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "AgentMessageTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Plan" + ], + "title": "PlanTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "raw_content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "summary_text": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "Reasoning" + ], + "title": "ReasoningTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary_text", + "type" + ], + "title": "ReasoningTurnItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "WebSearch" + ], + "title": "WebSearchTurnItemType", + "type": "string" + } + }, + "required": [ + "action", + "id", + "query", + "type" + ], + "title": "WebSearchTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "ContextCompaction" + ], + "title": "ContextCompactionTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionTurnItem", + "type": "object" + } + ] + }, + "UserInfoResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "allegedUserEmail": { + "type": [ + "string", + "null" + ] + } + }, + "title": "UserInfoResponse", + "type": "object" + }, + "UserInput": { + "description": "User input", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` that should be treated as special elements. These are byte ranges into the UTF-8 `text` buffer and are used to render or persist rich input markers (e.g., image placeholders) across history and resume without mutating the literal text.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "description": "Pre‑encoded data: URI image.", + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "description": "Local image path provided by the user. This will be converted to an `Image` variant (base64 data URL) during request serialization.", + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "local_image" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "description": "Skill selected by the user (name + path to SKILL.md).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "description": "Explicit mention selected by the user (name + app://connector id).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "UserSavedConfig": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "forcedChatgptWorkspaceId": { + "type": [ + "string", + "null" + ] + }, + "forcedLoginMethod": { + "anyOf": [ + { + "$ref": "#/definitions/ForcedLoginMethod" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelReasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "modelReasoningSummary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "modelVerbosity": { + "anyOf": [ + { + "$ref": "#/definitions/Verbosity" + }, + { + "type": "null" + } + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "profiles": { + "additionalProperties": { + "$ref": "#/definitions/Profile" + }, + "type": "object" + }, + "sandboxMode": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "sandboxSettings": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxSettings" + }, + { + "type": "null" + } + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/Tools" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "profiles" + ], + "type": "object" + }, + "V1ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "V1TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/V1ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "Verbosity": { + "description": "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "low", + "medium", + "high" + ], + "type": "string" + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + }, + "v2": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "Account": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyAccountType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ApiKeyAccount", + "type": "object" + }, + { + "properties": { + "email": { + "type": "string" + }, + "planType": { + "$ref": "#/definitions/v2/PlanType" + }, + "type": { + "enum": [ + "chatgpt" + ], + "title": "ChatgptAccountType", + "type": "string" + } + }, + "required": [ + "email", + "planType", + "type" + ], + "title": "ChatgptAccount", + "type": "object" + } + ] + }, + "AccountLoginCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "title": "AccountLoginCompletedNotification", + "type": "object" + }, + "AccountRateLimitsUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "rateLimits": { + "$ref": "#/definitions/v2/RateLimitSnapshot" + } + }, + "required": [ + "rateLimits" + ], + "title": "AccountRateLimitsUpdatedNotification", + "type": "object" + }, + "AccountUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authMode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AuthMode" + }, + { + "type": "null" + } + ] + } + }, + "title": "AccountUpdatedNotification", + "type": "object" + }, + "AgentMessageDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "AgentMessageDeltaNotification", + "type": "object" + }, + "AnalyticsConfig": { + "additionalProperties": true, + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/v2/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AppInfo": { + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "distributionChannel": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "installUrl": { + "type": [ + "string", + "null" + ] + }, + "isAccessible": { + "default": false, + "type": "boolean" + }, + "logoUrl": { + "type": [ + "string", + "null" + ] + }, + "logoUrlDark": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "AppsListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "AppsListParams", + "type": "object" + }, + "AppsListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/AppInfo" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "AppsListResponse", + "type": "object" + }, + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "enum": [ + "apikey" + ], + "type": "string" + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "enum": [ + "chatgpt" + ], + "type": "string" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "enum": [ + "chatgptAuthTokens" + ], + "type": "string" + } + ] + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CancelLoginAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "loginId": { + "type": "string" + } + }, + "required": [ + "loginId" + ], + "title": "CancelLoginAccountParams", + "type": "object" + }, + "CancelLoginAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "status": { + "$ref": "#/definitions/v2/CancelLoginAccountStatus" + } + }, + "required": [ + "status" + ], + "title": "CancelLoginAccountResponse", + "type": "object" + }, + "CancelLoginAccountStatus": { + "enum": [ + "canceled", + "notFound" + ], + "type": "string" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/v2/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/v2/ModeKind" + }, + "settings": { + "$ref": "#/definitions/v2/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "CollaborationModeListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - list collaboration mode presets.", + "title": "CollaborationModeListParams", + "type": "object" + }, + "CollaborationModeListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - collaboration mode presets response.", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/CollaborationModeMask" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "CollaborationModeListResponse", + "type": "object" + }, + "CollaborationModeMask": { + "description": "A mask for collaboration mode settings, allowing partial updates. All fields except `name` are optional, enabling selective updates.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "mode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ModeKind" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "timeoutMs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "command" + ], + "title": "CommandExecParams", + "type": "object" + }, + "CommandExecResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "exitCode": { + "format": "int32", + "type": "integer" + }, + "stderr": { + "type": "string" + }, + "stdout": { + "type": "string" + } + }, + "required": [ + "exitCode", + "stderr", + "stdout" + ], + "title": "CommandExecResponse", + "type": "object" + }, + "CommandExecutionOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "CommandExecutionOutputDeltaNotification", + "type": "object" + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "Config": { + "additionalProperties": true, + "properties": { + "analytics": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AnalyticsConfig" + }, + { + "type": "null" + } + ] + }, + "approval_policy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "compact_prompt": { + "type": [ + "string", + "null" + ] + }, + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "forced_chatgpt_workspace_id": { + "type": [ + "string", + "null" + ] + }, + "forced_login_method": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ForcedLoginMethod" + }, + { + "type": "null" + } + ] + }, + "instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "model_auto_compact_token_limit": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "model_provider": { + "type": [ + "string", + "null" + ] + }, + "model_reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "model_reasoning_summary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "model_verbosity": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Verbosity" + }, + { + "type": "null" + } + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "profiles": { + "additionalProperties": { + "$ref": "#/definitions/v2/ProfileV2" + }, + "default": {}, + "type": "object" + }, + "review_model": { + "type": [ + "string", + "null" + ] + }, + "sandbox_mode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "sandbox_workspace_write": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxWorkspaceWrite" + }, + { + "type": "null" + } + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ToolsV2" + }, + { + "type": "null" + } + ] + }, + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ConfigBatchWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "edits": { + "items": { + "$ref": "#/definitions/v2/ConfigEdit" + }, + "type": "array" + }, + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "edits" + ], + "title": "ConfigBatchWriteParams", + "type": "object" + }, + "ConfigEdit": { + "properties": { + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/v2/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "type": "object" + }, + "ConfigLayer": { + "properties": { + "config": true, + "disabledReason": { + "type": [ + "string", + "null" + ] + }, + "name": { + "$ref": "#/definitions/v2/ConfigLayerSource" + }, + "version": { + "type": "string" + } + }, + "required": [ + "config", + "name", + "version" + ], + "type": "object" + }, + "ConfigLayerMetadata": { + "properties": { + "name": { + "$ref": "#/definitions/v2/ConfigLayerSource" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "ConfigLayerSource": { + "oneOf": [ + { + "description": "Managed preferences layer delivered by MDM (macOS only).", + "properties": { + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "enum": [ + "mdm" + ], + "title": "MdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "domain", + "key", + "type" + ], + "title": "MdmConfigLayerSource", + "type": "object" + }, + { + "description": "Managed config layer from a file (usually `managed_config.toml`).", + "properties": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ], + "description": "This is the path to the system config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "system" + ], + "title": "SystemConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "SystemConfigLayerSource", + "type": "object" + }, + { + "description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", + "properties": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ], + "description": "This is the path to the user's config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "user" + ], + "title": "UserConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "UserConfigLayerSource", + "type": "object" + }, + { + "description": "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + "properties": { + "dotCodexFolder": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "type": { + "enum": [ + "project" + ], + "title": "ProjectConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "dotCodexFolder", + "type" + ], + "title": "ProjectConfigLayerSource", + "type": "object" + }, + { + "description": "Session-layer overrides supplied via `-c`/`--config`.", + "properties": { + "type": { + "enum": [ + "sessionFlags" + ], + "title": "SessionFlagsConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SessionFlagsConfigLayerSource", + "type": "object" + }, + { + "description": "`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a \"best effort\" while we phase out `managed_config.toml` in favor of `requirements.toml`.", + "properties": { + "file": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "type": { + "enum": [ + "legacyManagedConfigTomlFromFile" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSource", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "legacyManagedConfigTomlFromMdm" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSource", + "type": "object" + } + ] + }, + "ConfigReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwd": { + "description": "Optional working directory to resolve project config layers. If specified, return the effective config as seen from that directory (i.e., including any project layers between `cwd` and the project/repo root).", + "type": [ + "string", + "null" + ] + }, + "includeLayers": { + "default": false, + "type": "boolean" + } + }, + "title": "ConfigReadParams", + "type": "object" + }, + "ConfigReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "config": { + "$ref": "#/definitions/v2/Config" + }, + "layers": { + "items": { + "$ref": "#/definitions/v2/ConfigLayer" + }, + "type": [ + "array", + "null" + ] + }, + "origins": { + "additionalProperties": { + "$ref": "#/definitions/v2/ConfigLayerMetadata" + }, + "type": "object" + } + }, + "required": [ + "config", + "origins" + ], + "title": "ConfigReadResponse", + "type": "object" + }, + "ConfigRequirements": { + "properties": { + "allowedApprovalPolicies": { + "items": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "type": [ + "array", + "null" + ] + }, + "allowedSandboxModes": { + "items": { + "$ref": "#/definitions/v2/SandboxMode" + }, + "type": [ + "array", + "null" + ] + }, + "enforceResidency": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ResidencyRequirement" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ConfigRequirementsReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "requirements": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ConfigRequirements" + }, + { + "type": "null" + } + ], + "description": "Null if no requirements are configured (e.g. no requirements.toml/MDM entries)." + } + }, + "title": "ConfigRequirementsReadResponse", + "type": "object" + }, + "ConfigValueWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + }, + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/v2/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "title": "ConfigValueWriteParams", + "type": "object" + }, + "ConfigWarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "details": { + "description": "Optional extra guidance or error details.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Optional path to the config file that triggered the warning.", + "type": [ + "string", + "null" + ] + }, + "range": { + "anyOf": [ + { + "$ref": "#/definitions/v2/TextRange" + }, + { + "type": "null" + } + ], + "description": "Optional range for the error location inside the config file." + }, + "summary": { + "description": "Concise summary of the warning.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "title": "ConfigWarningNotification", + "type": "object" + }, + "ConfigWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "filePath": { + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ], + "description": "Canonical path to the config file that was written." + }, + "overriddenMetadata": { + "anyOf": [ + { + "$ref": "#/definitions/v2/OverriddenMetadata" + }, + { + "type": "null" + } + ] + }, + "status": { + "$ref": "#/definitions/v2/WriteStatus" + }, + "version": { + "type": "string" + } + }, + "required": [ + "filePath", + "status", + "version" + ], + "title": "ConfigWriteResponse", + "type": "object" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/v2/TextContent" + }, + { + "$ref": "#/definitions/v2/ImageContent" + }, + { + "$ref": "#/definitions/v2/AudioContent" + }, + { + "$ref": "#/definitions/v2/ResourceLink" + }, + { + "$ref": "#/definitions/v2/EmbeddedResource" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "ContextCompactedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "title": "ContextCompactedNotification", + "type": "object" + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "hasCredits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "hasCredits", + "unlimited" + ], + "type": "object" + }, + "DeprecationNoticeNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "title": "DeprecationNoticeNotification", + "type": "object" + }, + "DynamicToolSpec": { + "properties": { + "description": { + "type": "string" + }, + "inputSchema": true, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "inputSchema", + "name" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/v2/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/v2/TextResourceContents" + }, + { + "$ref": "#/definitions/v2/BlobResourceContents" + } + ] + }, + "ErrorNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "$ref": "#/definitions/v2/TurnError" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "willRetry": { + "type": "boolean" + } + }, + "required": [ + "error", + "threadId", + "turnId", + "willRetry" + ], + "title": "ErrorNotification", + "type": "object" + }, + "FeedbackUploadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "classification": { + "type": "string" + }, + "includeLogs": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "classification", + "includeLogs" + ], + "title": "FeedbackUploadParams", + "type": "object" + }, + "FeedbackUploadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "FeedbackUploadResponse", + "type": "object" + }, + "FileChangeOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "FileChangeOutputDeltaNotification", + "type": "object" + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/v2/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ForcedLoginMethod": { + "enum": [ + "chatgpt", + "api" + ], + "type": "string" + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/v2/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GetAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "refreshToken": { + "default": false, + "description": "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.", + "type": "boolean" + } + }, + "title": "GetAccountParams", + "type": "object" + }, + "GetAccountRateLimitsResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "rateLimits": { + "$ref": "#/definitions/v2/RateLimitSnapshot" + } + }, + "required": [ + "rateLimits" + ], + "title": "GetAccountRateLimitsResponse", + "type": "object" + }, + "GetAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "account": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Account" + }, + { + "type": "null" + } + ] + }, + "requiresOpenaiAuth": { + "type": "boolean" + } + }, + "required": [ + "requiresOpenaiAuth" + ], + "title": "GetAccountResponse", + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "ItemCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "item": { + "$ref": "#/definitions/v2/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "ItemCompletedNotification", + "type": "object" + }, + "ItemStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "item": { + "$ref": "#/definitions/v2/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "ItemStartedNotification", + "type": "object" + }, + "ListMcpServerStatusParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a server-defined value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ListMcpServerStatusParams", + "type": "object" + }, + "ListMcpServerStatusResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/McpServerStatus" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ListMcpServerStatusResponse", + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "LoginAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "apiKey": { + "type": "string" + }, + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "apiKey", + "type" + ], + "title": "ApiKeyv2::LoginAccountParams", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "Chatgptv2::LoginAccountParams", + "type": "object" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", + "properties": { + "accessToken": { + "description": "Access token (JWT) supplied by the client. This token is used for backend API requests.", + "type": "string" + }, + "idToken": { + "description": "ID token (JWT) supplied by the client.\n\nThis token is used for identity and account metadata (email, plan type, workspace id).", + "type": "string" + }, + "type": { + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "accessToken", + "idToken", + "type" + ], + "title": "ChatgptAuthTokensv2::LoginAccountParams", + "type": "object" + } + ], + "title": "LoginAccountParams" + }, + "LoginAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ApiKeyv2::LoginAccountResponse", + "type": "object" + }, + { + "properties": { + "authUrl": { + "description": "URL the client should open in a browser to initiate the OAuth flow.", + "type": "string" + }, + "loginId": { + "type": "string" + }, + "type": { + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "authUrl", + "loginId", + "type" + ], + "title": "Chatgptv2::LoginAccountResponse", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptAuthTokensv2::LoginAccountResponse", + "type": "object" + } + ], + "title": "LoginAccountResponse" + }, + "LogoutAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LogoutAccountResponse", + "type": "object" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "notLoggedIn", + "bearerToken", + "oAuth" + ], + "type": "string" + }, + "McpServerOauthLoginCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "name", + "success" + ], + "title": "McpServerOauthLoginCompletedNotification", + "type": "object" + }, + "McpServerOauthLoginParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "name": { + "type": "string" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "timeoutSecs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "name" + ], + "title": "McpServerOauthLoginParams", + "type": "object" + }, + "McpServerOauthLoginResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authorizationUrl": { + "type": "string" + } + }, + "required": [ + "authorizationUrl" + ], + "title": "McpServerOauthLoginResponse", + "type": "object" + }, + "McpServerRefreshResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerRefreshResponse", + "type": "object" + }, + "McpServerStatus": { + "properties": { + "authStatus": { + "$ref": "#/definitions/v2/McpAuthStatus" + }, + "name": { + "type": "string" + }, + "resourceTemplates": { + "items": { + "$ref": "#/definitions/v2/ResourceTemplate" + }, + "type": "array" + }, + "resources": { + "items": { + "$ref": "#/definitions/v2/Resource" + }, + "type": "array" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/v2/Tool" + }, + "type": "object" + } + }, + "required": [ + "authStatus", + "name", + "resourceTemplates", + "resources", + "tools" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallProgressNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "message": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "message", + "threadId", + "turnId" + ], + "title": "McpToolCallProgressNotification", + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/v2/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "MergeStrategy": { + "enum": [ + "replace", + "upsert" + ], + "type": "string" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "Model": { + "properties": { + "defaultReasoningEffort": { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDefault": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "supportedReasoningEfforts": { + "items": { + "$ref": "#/definitions/v2/ReasoningEffortOption" + }, + "type": "array" + }, + "supportsPersonality": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "defaultReasoningEffort", + "description", + "displayName", + "id", + "isDefault", + "model", + "supportedReasoningEfforts" + ], + "type": "object" + }, + "ModelListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ModelListParams", + "type": "object" + }, + "ModelListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/Model" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ModelListResponse", + "type": "object" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "OverriddenMetadata": { + "properties": { + "effectiveValue": true, + "message": { + "type": "string" + }, + "overridingLayer": { + "$ref": "#/definitions/v2/ConfigLayerMetadata" + } + }, + "required": [ + "effectiveValue", + "message", + "overridingLayer" + ], + "type": "object" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "Personality": { + "enum": [ + "friendly", + "pragmatic" + ], + "type": "string" + }, + "PlanDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "PlanDeltaNotification", + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "ProfileV2": { + "additionalProperties": true, + "properties": { + "approval_policy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "chatgpt_base_url": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "model_provider": { + "type": [ + "string", + "null" + ] + }, + "model_reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "model_reasoning_summary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "model_verbosity": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Verbosity" + }, + { + "type": "null" + } + ] + }, + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/v2/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resetsAt": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "usedPercent": { + "format": "int32", + "type": "integer" + }, + "windowDurationMins": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "usedPercent" + ], + "type": "object" + }, + "RawResponseItemCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "item": { + "$ref": "#/definitions/v2/ResponseItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "RawResponseItemCompletedNotification", + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningEffortOption": { + "properties": { + "description": { + "type": "string" + }, + "reasoningEffort": { + "$ref": "#/definitions/v2/ReasoningEffort" + } + }, + "required": [ + "description", + "reasoningEffort" + ], + "type": "object" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "ReasoningSummaryPartAddedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "title": "ReasoningSummaryPartAddedNotification", + "type": "object" + }, + "ReasoningSummaryTextDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "title": "ReasoningSummaryTextDeltaNotification", + "type": "object" + }, + "ReasoningTextDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "contentIndex": { + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "contentIndex", + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "ReasoningTextDeltaNotification", + "type": "object" + }, + "ResidencyRequirement": { + "enum": [ + "us" + ], + "type": "string" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/v2/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/v2/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/v2/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/v2/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/v2/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/v2/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/v2/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "ReviewDelivery": { + "enum": [ + "inline", + "detached" + ], + "type": "string" + }, + "ReviewStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delivery": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReviewDelivery" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`)." + }, + "target": { + "$ref": "#/definitions/v2/ReviewTarget" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "target", + "threadId" + ], + "title": "ReviewStartParams", + "type": "object" + }, + "ReviewStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "reviewThreadId": { + "description": "Identifies the thread where the review runs.\n\nFor inline reviews, this is the original thread id. For detached reviews, this is the id of the new review thread.", + "type": "string" + }, + "turn": { + "$ref": "#/definitions/v2/Turn" + } + }, + "required": [ + "reviewThreadId", + "turn" + ], + "title": "ReviewStartResponse", + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions, equivalent to the old free-form prompt.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/v2/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SandboxWorkspaceWrite": { + "properties": { + "exclude_slash_tmp": { + "default": false, + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "type": "boolean" + }, + "network_access": { + "default": false, + "type": "boolean" + }, + "writable_roots": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/v2/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/v2/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brandColor": { + "type": [ + "string", + "null" + ] + }, + "defaultPrompt": { + "type": [ + "string", + "null" + ] + }, + "displayName": { + "type": [ + "string", + "null" + ] + }, + "iconLarge": { + "type": [ + "string", + "null" + ] + }, + "iconSmall": { + "type": [ + "string", + "null" + ] + }, + "shortDescription": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/v2/SkillScope" + }, + "shortDescription": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsConfigWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "enabled": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "enabled", + "path" + ], + "title": "SkillsConfigWriteParams", + "type": "object" + }, + "SkillsConfigWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "effectiveEnabled": { + "type": "boolean" + } + }, + "required": [ + "effectiveEnabled" + ], + "title": "SkillsConfigWriteResponse", + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/v2/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/v2/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "SkillsListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwds": { + "description": "When empty, defaults to the current session working directory.", + "items": { + "type": "string" + }, + "type": "array" + }, + "forceReload": { + "description": "When true, bypass the skills cache and re-scan skills from disk.", + "type": "boolean" + } + }, + "title": "SkillsListParams", + "type": "object" + }, + "SkillsListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/SkillsListEntry" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "SkillsListResponse", + "type": "object" + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/v2/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TerminalInteractionNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "processId": { + "type": "string" + }, + "stdin": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "processId", + "stdin", + "threadId", + "turnId" + ], + "title": "TerminalInteractionNotification", + "type": "object" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/v2/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextPosition": { + "properties": { + "column": { + "description": "1-based column number (in Unicode scalar values).", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "line": { + "description": "1-based line number.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "column", + "line" + ], + "type": "object" + }, + "TextRange": { + "properties": { + "end": { + "$ref": "#/definitions/v2/TextPosition" + }, + "start": { + "$ref": "#/definitions/v2/TextPosition" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/v2/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/v2/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/v2/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadArchiveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadArchiveParams", + "type": "object" + }, + "ThreadArchiveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadArchiveResponse", + "type": "object" + }, + "ThreadForkParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the forked thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Specify the rollout path to fork from. If specified, the thread_id param will be ignored.", + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadForkParams", + "type": "object" + }, + "ThreadForkResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/v2/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadForkResponse", + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/v2/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/v2/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/v2/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/v2/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/v2/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/v2/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/v2/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/v2/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/v2/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "ThreadListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "archived": { + "description": "Optional archived filter; when set to true, only archived threads are returned. If false or null, only non-archived threads are returned.", + "type": [ + "boolean", + "null" + ] + }, + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "modelProviders": { + "description": "Optional provider filter; when set, only sessions recorded under these providers are returned. When present but empty, includes all providers.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "sortKey": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadSortKey" + }, + { + "type": "null" + } + ], + "description": "Optional sort key; defaults to created_at." + }, + "sourceKinds": { + "description": "Optional source filter; when set, only sessions from these source kinds are returned. When omitted or empty, defaults to interactive sources.", + "items": { + "$ref": "#/definitions/v2/ThreadSourceKind" + }, + "type": [ + "array", + "null" + ] + } + }, + "title": "ThreadListParams", + "type": "object" + }, + "ThreadListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/Thread" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ThreadListResponse", + "type": "object" + }, + "ThreadLoadedListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to no limit.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ThreadLoadedListParams", + "type": "object" + }, + "ThreadLoadedListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "description": "Thread ids for sessions currently loaded in memory.", + "items": { + "type": "string" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ThreadLoadedListResponse", + "type": "object" + }, + "ThreadNameUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "threadName": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "threadId" + ], + "title": "ThreadNameUpdatedNotification", + "type": "object" + }, + "ThreadReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "includeTurns": { + "default": false, + "description": "When true, include turns and their items from rollout history.", + "type": "boolean" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadReadParams", + "type": "object" + }, + "ThreadReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "$ref": "#/definitions/v2/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadReadResponse", + "type": "object" + }, + "ThreadResumeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "history": { + "description": "[UNSTABLE] FOR CODEX CLOUD - DO NOT USE. If specified, the thread will be resumed with the provided history instead of loaded from disk.", + "items": { + "$ref": "#/definitions/v2/ResponseItem" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the resumed thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Specify the rollout path to resume from. If specified, the thread_id param will be ignored.", + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadResumeParams", + "type": "object" + }, + "ThreadResumeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/v2/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadResumeResponse", + "type": "object" + }, + "ThreadRollbackParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "numTurns": { + "description": "The number of turns to drop from the end of the thread. Must be >= 1.\n\nThis only modifies the thread's history and does not revert local file changes that have been made by the agent. Clients are responsible for reverting these changes.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "numTurns", + "threadId" + ], + "title": "ThreadRollbackParams", + "type": "object" + }, + "ThreadRollbackResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "allOf": [ + { + "$ref": "#/definitions/v2/Thread" + } + ], + "description": "The updated thread after applying the rollback, with `turns` populated.\n\nThe ThreadItems stored in each Turn are lossy since we explicitly do not persist all agent interactions, such as command executions. This is the same behavior as `thread/resume`." + } + }, + "required": [ + "thread" + ], + "title": "ThreadRollbackResponse", + "type": "object" + }, + "ThreadSetNameParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "name": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "name", + "threadId" + ], + "title": "ThreadSetNameParams", + "type": "object" + }, + "ThreadSetNameResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadSetNameResponse", + "type": "object" + }, + "ThreadSortKey": { + "enum": [ + "created_at", + "updated_at" + ], + "type": "string" + }, + "ThreadSourceKind": { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "subAgent", + "subAgentReview", + "subAgentCompact", + "subAgentThreadSpawn", + "subAgentOther", + "unknown" + ], + "type": "string" + }, + "ThreadStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "dynamicTools": { + "items": { + "$ref": "#/definitions/v2/DynamicToolSpec" + }, + "type": [ + "array", + "null" + ] + }, + "ephemeral": { + "type": [ + "boolean", + "null" + ] + }, + "experimentalRawEvents": { + "default": false, + "description": "If true, opt into emitting raw response items on the event stream.\n\nThis is for internal use only (e.g. Codex Cloud). (TODO): Figure out a better way to categorize internal / experimental events & protocols.", + "type": "boolean" + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + } + }, + "title": "ThreadStartParams", + "type": "object" + }, + "ThreadStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/v2/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadStartResponse", + "type": "object" + }, + "ThreadStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "$ref": "#/definitions/v2/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadStartedNotification", + "type": "object" + }, + "ThreadTokenUsage": { + "properties": { + "last": { + "$ref": "#/definitions/v2/TokenUsageBreakdown" + }, + "modelContextWindow": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total": { + "$ref": "#/definitions/v2/TokenUsageBreakdown" + } + }, + "required": [ + "last", + "total" + ], + "type": "object" + }, + "ThreadTokenUsageUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "tokenUsage": { + "$ref": "#/definitions/v2/ThreadTokenUsage" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "tokenUsage", + "turnId" + ], + "title": "ThreadTokenUsageUpdatedNotification", + "type": "object" + }, + "ThreadUnarchiveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadUnarchiveParams", + "type": "object" + }, + "ThreadUnarchiveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "$ref": "#/definitions/v2/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadUnarchiveResponse", + "type": "object" + }, + "TokenUsageBreakdown": { + "properties": { + "cachedInputTokens": { + "format": "int64", + "type": "integer" + }, + "inputTokens": { + "format": "int64", + "type": "integer" + }, + "outputTokens": { + "format": "int64", + "type": "integer" + }, + "reasoningOutputTokens": { + "format": "int64", + "type": "integer" + }, + "totalTokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cachedInputTokens", + "inputTokens", + "outputTokens", + "reasoningOutputTokens", + "totalTokens" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/v2/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolsV2": { + "properties": { + "view_image": { + "type": [ + "boolean", + "null" + ] + }, + "web_search": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/v2/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/v2/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/v2/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/v2/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "title": "TurnCompletedNotification", + "type": "object" + }, + "TurnDiffUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Notification that the turn-level unified diff has changed. Contains the latest aggregated diff across all file changes in the turn.", + "properties": { + "diff": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "diff", + "threadId", + "turnId" + ], + "title": "TurnDiffUpdatedNotification", + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/v2/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnInterruptParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "title": "TurnInterruptParams", + "type": "object" + }, + "TurnInterruptResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnInterruptResponse", + "type": "object" + }, + "TurnPlanStep": { + "properties": { + "status": { + "$ref": "#/definitions/v2/TurnPlanStepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "TurnPlanStepStatus": { + "enum": [ + "pending", + "inProgress", + "completed" + ], + "type": "string" + }, + "TurnPlanUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "explanation": { + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/v2/TurnPlanStep" + }, + "type": "array" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "plan", + "threadId", + "turnId" + ], + "title": "TurnPlanUpdatedNotification", + "type": "object" + }, + "TurnStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "collaborationMode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/CollaborationMode" + }, + { + "type": "null" + } + ], + "description": "EXPERIMENTAL - set a pre-set collaboration mode. Takes precedence over model, reasoning_effort, and developer instructions if set." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/v2/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "title": "TurnStartParams", + "type": "object" + }, + "TurnStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "turn": { + "$ref": "#/definitions/v2/Turn" + } + }, + "required": [ + "turn" + ], + "title": "TurnStartResponse", + "type": "object" + }, + "TurnStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/v2/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "title": "TurnStartedNotification", + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/v2/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "Verbosity": { + "description": "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "low", + "medium", + "high" + ], + "type": "string" + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + }, + "WebSearchMode": { + "enum": [ + "disabled", + "cached", + "live" + ], + "type": "string" + }, + "WindowsWorldWritableWarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "extraCount": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "failedScan": { + "type": "boolean" + }, + "samplePaths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "extraCount", + "failedScan", + "samplePaths" + ], + "title": "WindowsWorldWritableWarningNotification", + "type": "object" + }, + "WriteStatus": { + "enum": [ + "ok", + "okOverridden" + ], + "type": "string" + } + } + }, + "title": "CodexAppServerProtocol", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/AddConversationListenerParams.json b/codex-rs/app-server-protocol/schema/json/v1/AddConversationListenerParams.json new file mode 100644 index 0000000000..67b8bd7d81 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/AddConversationListenerParams.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ThreadId": { + "type": "string" + } + }, + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "experimentalRawEvents": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "conversationId" + ], + "title": "AddConversationListenerParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/AddConversationSubscriptionResponse.json b/codex-rs/app-server-protocol/schema/json/v1/AddConversationSubscriptionResponse.json new file mode 100644 index 0000000000..8bd1bcf016 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/AddConversationSubscriptionResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "subscriptionId": { + "type": "string" + } + }, + "required": [ + "subscriptionId" + ], + "title": "AddConversationSubscriptionResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationParams.json b/codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationParams.json new file mode 100644 index 0000000000..7ee5b16b3b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationParams.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ThreadId": { + "type": "string" + } + }, + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "rolloutPath" + ], + "title": "ArchiveConversationParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationResponse.json new file mode 100644 index 0000000000..253d15cc0d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ArchiveConversationResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ArchiveConversationResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/AuthStatusChangeNotification.json b/codex-rs/app-server-protocol/schema/json/v1/AuthStatusChangeNotification.json new file mode 100644 index 0000000000..2aee28ba9c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/AuthStatusChangeNotification.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "enum": [ + "apikey" + ], + "type": "string" + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "enum": [ + "chatgpt" + ], + "type": "string" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "enum": [ + "chatgptAuthTokens" + ], + "type": "string" + } + ] + } + }, + "description": "Deprecated notification. Use AccountUpdatedNotification instead.", + "properties": { + "authMethod": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + } + }, + "title": "AuthStatusChangeNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptParams.json b/codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptParams.json new file mode 100644 index 0000000000..8367dac087 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "loginId": { + "type": "string" + } + }, + "required": [ + "loginId" + ], + "title": "CancelLoginChatGptParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptResponse.json b/codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptResponse.json new file mode 100644 index 0000000000..a4e1c333c4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/CancelLoginChatGptResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CancelLoginChatGptResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandParams.json b/codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandParams.json new file mode 100644 index 0000000000..a325704be4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandParams.json @@ -0,0 +1,158 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + } + }, + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "timeoutMs": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "command" + ], + "title": "ExecOneOffCommandParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandResponse.json new file mode 100644 index 0000000000..121ed648ff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ExecOneOffCommandResponse.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "exitCode": { + "format": "int32", + "type": "integer" + }, + "stderr": { + "type": "string" + }, + "stdout": { + "type": "string" + } + }, + "required": [ + "exitCode", + "stderr", + "stdout" + ], + "title": "ExecOneOffCommandResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationParams.json b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationParams.json new file mode 100644 index 0000000000..bc84249516 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationParams.json @@ -0,0 +1,159 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "NewConversationParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "compactPrompt": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "includeApplyPatchTool": { + "type": [ + "boolean", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "ThreadId": { + "type": "string" + } + }, + "properties": { + "conversationId": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "overrides": { + "anyOf": [ + { + "$ref": "#/definitions/NewConversationParams" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": [ + "string", + "null" + ] + } + }, + "title": "ForkConversationParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json new file mode 100644 index 0000000000..f4c68a9f9d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json @@ -0,0 +1,5358 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AgentMessageContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Text" + ], + "title": "TextAgentMessageContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextAgentMessageContent", + "type": "object" + } + ] + }, + "AgentStatus": { + "description": "Agent lifecycle status, derived from emitted events.", + "oneOf": [ + { + "description": "Agent is waiting for initialization.", + "enum": [ + "pending_init" + ], + "type": "string" + }, + { + "description": "Agent is currently running.", + "enum": [ + "running" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Agent is done. Contains the final assistant message.", + "properties": { + "completed": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "completed" + ], + "title": "CompletedAgentStatus", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Agent encountered an error.", + "properties": { + "errored": { + "type": "string" + } + }, + "required": [ + "errored" + ], + "title": "ErroredAgentStatus", + "type": "object" + }, + { + "description": "Agent has been shutdown.", + "enum": [ + "shutdown" + ], + "type": "string" + }, + { + "description": "Agent is not found.", + "enum": [ + "not_found" + ], + "type": "string" + } + ] + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "Codex errors that we expose to clients.", + "oneOf": [ + { + "enum": [ + "context_window_exceeded", + "usage_limit_exceeded", + "internal_server_error", + "unauthorized", + "bad_request", + "sandbox_error", + "thread_rollback_failed", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "model_cap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "model_cap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "http_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "http_connection_failed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "response_stream_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_connection_failed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turnbefore completion.", + "properties": { + "response_stream_disconnected": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_disconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "response_too_many_failed_attempts": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_too_many_failed_attempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "has_credits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "has_credits", + "unlimited" + ], + "type": "object" + }, + "CustomPrompt": { + "properties": { + "argument_hint": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "Duration": { + "properties": { + "nanos": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "secs": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "nanos", + "secs" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "EventMsg": { + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": "code" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the associated exec call, if available.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + } + ] + }, + "ExecCommandSource": { + "enum": [ + "agent", + "user_shell", + "unified_exec_startup", + "unified_exec_interaction" + ], + "type": "string" + }, + "ExecOutputStream": { + "enum": [ + "stdout", + "stderr" + ], + "type": "string" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "HistoryEntry": { + "properties": { + "conversation_id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "ts": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "conversation_id", + "text", + "ts" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "not_logged_in", + "bearer_token", + "o_auth" + ], + "type": "string" + }, + "McpInvocation": { + "properties": { + "arguments": { + "description": "Arguments to the tool call." + }, + "server": { + "description": "Name of the MCP server as defined in the config.", + "type": "string" + }, + "tool": { + "description": "Name of the tool as given by the MCP server.", + "type": "string" + } + }, + "required": [ + "server", + "tool" + ], + "type": "object" + }, + "McpStartupFailure": { + "properties": { + "error": { + "type": "string" + }, + "server": { + "type": "string" + } + }, + "required": [ + "error", + "server" + ], + "type": "object" + }, + "McpStartupStatus": { + "oneOf": [ + { + "properties": { + "state": { + "enum": [ + "starting" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "ready" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus2", + "type": "object" + }, + { + "properties": { + "error": { + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "error", + "state" + ], + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "cancelled" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus3", + "type": "object" + } + ] + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "PlanItemArg": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/definitions/StepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "plan_type": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resets_at": { + "description": "Unix timestamp (seconds since epoch) when the window resets.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "used_percent": { + "description": "Percentage (0-100) of the window that has been consumed.", + "format": "double", + "type": "number" + }, + "window_minutes": { + "description": "Rolling window duration, in minutes.", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "used_percent" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "RequestUserInputQuestion": { + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestionOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "RequestUserInputQuestionOption": { + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "Result_of_CallToolResult_or_String": { + "oneOf": [ + { + "properties": { + "Ok": { + "$ref": "#/definitions/CallToolResult" + } + }, + "required": [ + "Ok" + ], + "title": "OkResult_of_CallToolResult_or_String", + "type": "object" + }, + { + "properties": { + "Err": { + "type": "string" + } + }, + "required": [ + "Err" + ], + "title": "ErrResult_of_CallToolResult_or_String", + "type": "object" + } + ] + }, + "ReviewCodeLocation": { + "description": "Location of the code related to a review finding.", + "properties": { + "absolute_file_path": { + "type": "string" + }, + "line_range": { + "$ref": "#/definitions/ReviewLineRange" + } + }, + "required": [ + "absolute_file_path", + "line_range" + ], + "type": "object" + }, + "ReviewFinding": { + "description": "A single review finding describing an observed issue or recommendation.", + "properties": { + "body": { + "type": "string" + }, + "code_location": { + "$ref": "#/definitions/ReviewCodeLocation" + }, + "confidence_score": { + "format": "float", + "type": "number" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "body", + "code_location", + "confidence_score", + "priority", + "title" + ], + "type": "object" + }, + "ReviewLineRange": { + "description": "Inclusive line range in a file associated with the finding.", + "properties": { + "end": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ReviewOutputEvent": { + "description": "Structured review result produced by a child review session.", + "properties": { + "findings": { + "items": { + "$ref": "#/definitions/ReviewFinding" + }, + "type": "array" + }, + "overall_confidence_score": { + "format": "float", + "type": "number" + }, + "overall_correctness": { + "type": "string" + }, + "overall_explanation": { + "type": "string" + } + }, + "required": [ + "findings", + "overall_confidence_score", + "overall_correctness", + "overall_explanation" + ], + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions provided by the user.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brand_color": { + "type": [ + "string", + "null" + ] + }, + "default_prompt": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "icon_large": { + "type": [ + "string", + "null" + ] + }, + "icon_small": { + "type": [ + "string", + "null" + ] + }, + "short_description": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "short_description": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "StepStatus": { + "enum": [ + "pending", + "in_progress", + "completed" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byte_range": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byte_range" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "TokenUsage": { + "properties": { + "cached_input_tokens": { + "format": "int64", + "type": "integer" + }, + "input_tokens": { + "format": "int64", + "type": "integer" + }, + "output_tokens": { + "format": "int64", + "type": "integer" + }, + "reasoning_output_tokens": { + "format": "int64", + "type": "integer" + }, + "total_tokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cached_input_tokens", + "input_tokens", + "output_tokens", + "reasoning_output_tokens", + "total_tokens" + ], + "type": "object" + }, + "TokenUsageInfo": { + "properties": { + "last_token_usage": { + "$ref": "#/definitions/TokenUsage" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total_token_usage": { + "$ref": "#/definitions/TokenUsage" + } + }, + "required": [ + "last_token_usage", + "total_token_usage" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + }, + "TurnItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "UserMessage" + ], + "title": "UserMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageTurnItem", + "type": "object" + }, + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/AgentMessageContent" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "AgentMessage" + ], + "title": "AgentMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "AgentMessageTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Plan" + ], + "title": "PlanTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "raw_content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "summary_text": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "Reasoning" + ], + "title": "ReasoningTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary_text", + "type" + ], + "title": "ReasoningTurnItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "WebSearch" + ], + "title": "WebSearchTurnItemType", + "type": "string" + } + }, + "required": [ + "action", + "id", + "query", + "type" + ], + "title": "WebSearchTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "ContextCompaction" + ], + "title": "ContextCompactionTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionTurnItem", + "type": "object" + } + ] + }, + "UserInput": { + "description": "User input", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` that should be treated as special elements. These are byte ranges into the UTF-8 `text` buffer and are used to render or persist rich input markers (e.g., image placeholders) across history and resume without mutating the literal text.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "description": "Pre‑encoded data: URI image.", + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "description": "Local image path provided by the user. This will be converted to an `Image` variant (base64 data URL) during request serialization.", + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "local_image" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "description": "Skill selected by the user (name + path to SKILL.md).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "description": "Explicit mention selected by the user (name + app://connector id).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "initialMessages": { + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "type": "string" + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "model", + "rolloutPath" + ], + "title": "ForkConversationResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusParams.json b/codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusParams.json new file mode 100644 index 0000000000..0b21fd765d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusParams.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "includeToken": { + "type": [ + "boolean", + "null" + ] + }, + "refreshToken": { + "type": [ + "boolean", + "null" + ] + } + }, + "title": "GetAuthStatusParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusResponse.json b/codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusResponse.json new file mode 100644 index 0000000000..7a605453d4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GetAuthStatusResponse.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "enum": [ + "apikey" + ], + "type": "string" + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "enum": [ + "chatgpt" + ], + "type": "string" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "enum": [ + "chatgptAuthTokens" + ], + "type": "string" + } + ] + } + }, + "properties": { + "authMethod": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + }, + "authToken": { + "type": [ + "string", + "null" + ] + }, + "requiresOpenaiAuth": { + "type": [ + "boolean", + "null" + ] + } + }, + "title": "GetAuthStatusResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryParams.json b/codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryParams.json new file mode 100644 index 0000000000..aa6726cded --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryParams.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "anyOf": [ + { + "properties": { + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "rolloutPath" + ], + "title": "RolloutPathv1::GetConversationSummaryParams", + "type": "object" + }, + { + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "conversationId" + ], + "title": "ConversationIdv1::GetConversationSummaryParams", + "type": "object" + } + ], + "definitions": { + "ThreadId": { + "type": "string" + } + }, + "title": "GetConversationSummaryParams" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryResponse.json b/codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryResponse.json new file mode 100644 index 0000000000..954ac28ed1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GetConversationSummaryResponse.json @@ -0,0 +1,175 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ConversationGitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "origin_url": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ConversationSummary": { + "properties": { + "cliVersion": { + "type": "string" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/ConversationGitInfo" + }, + { + "type": "null" + } + ] + }, + "modelProvider": { + "type": "string" + }, + "path": { + "type": "string" + }, + "preview": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/SessionSource" + }, + "timestamp": { + "type": [ + "string", + "null" + ] + }, + "updatedAt": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "cliVersion", + "conversationId", + "cwd", + "modelProvider", + "path", + "preview", + "source" + ], + "type": "object" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "mcp", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subagent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subagent" + ], + "title": "SubagentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "ThreadId": { + "type": "string" + } + }, + "properties": { + "summary": { + "$ref": "#/definitions/ConversationSummary" + } + }, + "required": [ + "summary" + ], + "title": "GetConversationSummaryResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GetUserAgentResponse.json b/codex-rs/app-server-protocol/schema/json/v1/GetUserAgentResponse.json new file mode 100644 index 0000000000..c041282c8f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GetUserAgentResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "userAgent": { + "type": "string" + } + }, + "required": [ + "userAgent" + ], + "title": "GetUserAgentResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json b/codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json new file mode 100644 index 0000000000..b5e472b6ce --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json @@ -0,0 +1,330 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "ForcedLoginMethod": { + "enum": [ + "chatgpt", + "api" + ], + "type": "string" + }, + "Profile": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "chatgptBaseUrl": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "modelReasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "modelReasoningSummary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "modelVerbosity": { + "anyOf": [ + { + "$ref": "#/definitions/Verbosity" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "SandboxSettings": { + "properties": { + "excludeSlashTmp": { + "type": [ + "boolean", + "null" + ] + }, + "excludeTmpdirEnvVar": { + "type": [ + "boolean", + "null" + ] + }, + "networkAccess": { + "type": [ + "boolean", + "null" + ] + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "type": "object" + }, + "Tools": { + "properties": { + "viewImage": { + "type": [ + "boolean", + "null" + ] + }, + "webSearch": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "UserSavedConfig": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "forcedChatgptWorkspaceId": { + "type": [ + "string", + "null" + ] + }, + "forcedLoginMethod": { + "anyOf": [ + { + "$ref": "#/definitions/ForcedLoginMethod" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelReasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "modelReasoningSummary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "modelVerbosity": { + "anyOf": [ + { + "$ref": "#/definitions/Verbosity" + }, + { + "type": "null" + } + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "profiles": { + "additionalProperties": { + "$ref": "#/definitions/Profile" + }, + "type": "object" + }, + "sandboxMode": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "sandboxSettings": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxSettings" + }, + { + "type": "null" + } + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/Tools" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "profiles" + ], + "type": "object" + }, + "Verbosity": { + "description": "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "low", + "medium", + "high" + ], + "type": "string" + } + }, + "properties": { + "config": { + "$ref": "#/definitions/UserSavedConfig" + } + }, + "required": [ + "config" + ], + "title": "GetUserSavedConfigResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteParams.json b/codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteParams.json new file mode 100644 index 0000000000..51640965ca --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwd": { + "type": "string" + } + }, + "required": [ + "cwd" + ], + "title": "GitDiffToRemoteParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteResponse.json b/codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteResponse.json new file mode 100644 index 0000000000..fd59b80e94 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/GitDiffToRemoteResponse.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "GitSha": { + "type": "string" + } + }, + "properties": { + "diff": { + "type": "string" + }, + "sha": { + "$ref": "#/definitions/GitSha" + } + }, + "required": [ + "diff", + "sha" + ], + "title": "GitDiffToRemoteResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json b/codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json new file mode 100644 index 0000000000..06dd68dd59 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ClientInfo": { + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + } + }, + "properties": { + "clientInfo": { + "$ref": "#/definitions/ClientInfo" + } + }, + "required": [ + "clientInfo" + ], + "title": "InitializeParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json b/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json new file mode 100644 index 0000000000..6ace3177ba --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "userAgent": { + "type": "string" + } + }, + "required": [ + "userAgent" + ], + "title": "InitializeResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/InterruptConversationParams.json b/codex-rs/app-server-protocol/schema/json/v1/InterruptConversationParams.json new file mode 100644 index 0000000000..4fdd221fca --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/InterruptConversationParams.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ThreadId": { + "type": "string" + } + }, + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "conversationId" + ], + "title": "InterruptConversationParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/InterruptConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/InterruptConversationResponse.json new file mode 100644 index 0000000000..5d2ddf3e40 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/InterruptConversationResponse.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + } + }, + "properties": { + "abortReason": { + "$ref": "#/definitions/TurnAbortReason" + } + }, + "required": [ + "abortReason" + ], + "title": "InterruptConversationResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ListConversationsParams.json b/codex-rs/app-server-protocol/schema/json/v1/ListConversationsParams.json new file mode 100644 index 0000000000..9ac05602c4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ListConversationsParams.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "type": [ + "string", + "null" + ] + }, + "modelProviders": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "pageSize": { + "format": "uint", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ListConversationsParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ListConversationsResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ListConversationsResponse.json new file mode 100644 index 0000000000..b7e3b8f8f1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ListConversationsResponse.json @@ -0,0 +1,184 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ConversationGitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "origin_url": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ConversationSummary": { + "properties": { + "cliVersion": { + "type": "string" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/ConversationGitInfo" + }, + { + "type": "null" + } + ] + }, + "modelProvider": { + "type": "string" + }, + "path": { + "type": "string" + }, + "preview": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/SessionSource" + }, + "timestamp": { + "type": [ + "string", + "null" + ] + }, + "updatedAt": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "cliVersion", + "conversationId", + "cwd", + "modelProvider", + "path", + "preview", + "source" + ], + "type": "object" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "mcp", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subagent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subagent" + ], + "title": "SubagentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "ThreadId": { + "type": "string" + } + }, + "properties": { + "items": { + "items": { + "$ref": "#/definitions/ConversationSummary" + }, + "type": "array" + }, + "nextCursor": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "items" + ], + "title": "ListConversationsResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyParams.json b/codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyParams.json new file mode 100644 index 0000000000..b23ce5548f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "apiKey": { + "type": "string" + } + }, + "required": [ + "apiKey" + ], + "title": "LoginApiKeyParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyResponse.json b/codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyResponse.json new file mode 100644 index 0000000000..cba1fe3c40 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/LoginApiKeyResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LoginApiKeyResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/LoginChatGptCompleteNotification.json b/codex-rs/app-server-protocol/schema/json/v1/LoginChatGptCompleteNotification.json new file mode 100644 index 0000000000..ae34de2f42 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/LoginChatGptCompleteNotification.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Deprecated in favor of AccountLoginCompletedNotification.", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "loginId", + "success" + ], + "title": "LoginChatGptCompleteNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/LoginChatGptResponse.json b/codex-rs/app-server-protocol/schema/json/v1/LoginChatGptResponse.json new file mode 100644 index 0000000000..9ecf3cdbb9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/LoginChatGptResponse.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authUrl": { + "type": "string" + }, + "loginId": { + "type": "string" + } + }, + "required": [ + "authUrl", + "loginId" + ], + "title": "LoginChatGptResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/LogoutChatGptResponse.json b/codex-rs/app-server-protocol/schema/json/v1/LogoutChatGptResponse.json new file mode 100644 index 0000000000..449cfc5fab --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/LogoutChatGptResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LogoutChatGptResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/NewConversationParams.json b/codex-rs/app-server-protocol/schema/json/v1/NewConversationParams.json new file mode 100644 index 0000000000..167aee56e9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/NewConversationParams.json @@ -0,0 +1,125 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + } + }, + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "compactPrompt": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "includeApplyPatchTool": { + "type": [ + "boolean", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + } + }, + "title": "NewConversationParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/NewConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/NewConversationResponse.json new file mode 100644 index 0000000000..8030d8113f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/NewConversationResponse.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ThreadId": { + "type": "string" + } + }, + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "model": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "model", + "rolloutPath" + ], + "title": "NewConversationResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/RemoveConversationListenerParams.json b/codex-rs/app-server-protocol/schema/json/v1/RemoveConversationListenerParams.json new file mode 100644 index 0000000000..990b8ff0c4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/RemoveConversationListenerParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "subscriptionId": { + "type": "string" + } + }, + "required": [ + "subscriptionId" + ], + "title": "RemoveConversationListenerParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/RemoveConversationSubscriptionResponse.json b/codex-rs/app-server-protocol/schema/json/v1/RemoveConversationSubscriptionResponse.json new file mode 100644 index 0000000000..8efe40d430 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/RemoveConversationSubscriptionResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RemoveConversationSubscriptionResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json new file mode 100644 index 0000000000..e1cc626cc8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json @@ -0,0 +1,915 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "NewConversationParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "compactPrompt": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "includeApplyPatchTool": { + "type": [ + "boolean", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "ThreadId": { + "type": "string" + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "conversationId": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history": { + "items": { + "$ref": "#/definitions/ResponseItem" + }, + "type": [ + "array", + "null" + ] + }, + "overrides": { + "anyOf": [ + { + "$ref": "#/definitions/NewConversationParams" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": [ + "string", + "null" + ] + } + }, + "title": "ResumeConversationParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json new file mode 100644 index 0000000000..fcb39cd05c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json @@ -0,0 +1,5358 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AgentMessageContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Text" + ], + "title": "TextAgentMessageContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextAgentMessageContent", + "type": "object" + } + ] + }, + "AgentStatus": { + "description": "Agent lifecycle status, derived from emitted events.", + "oneOf": [ + { + "description": "Agent is waiting for initialization.", + "enum": [ + "pending_init" + ], + "type": "string" + }, + { + "description": "Agent is currently running.", + "enum": [ + "running" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Agent is done. Contains the final assistant message.", + "properties": { + "completed": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "completed" + ], + "title": "CompletedAgentStatus", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Agent encountered an error.", + "properties": { + "errored": { + "type": "string" + } + }, + "required": [ + "errored" + ], + "title": "ErroredAgentStatus", + "type": "object" + }, + { + "description": "Agent has been shutdown.", + "enum": [ + "shutdown" + ], + "type": "string" + }, + { + "description": "Agent is not found.", + "enum": [ + "not_found" + ], + "type": "string" + } + ] + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "Codex errors that we expose to clients.", + "oneOf": [ + { + "enum": [ + "context_window_exceeded", + "usage_limit_exceeded", + "internal_server_error", + "unauthorized", + "bad_request", + "sandbox_error", + "thread_rollback_failed", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "model_cap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "model_cap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "http_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "http_connection_failed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "response_stream_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_connection_failed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turnbefore completion.", + "properties": { + "response_stream_disconnected": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_disconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "response_too_many_failed_attempts": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_too_many_failed_attempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "has_credits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "has_credits", + "unlimited" + ], + "type": "object" + }, + "CustomPrompt": { + "properties": { + "argument_hint": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "Duration": { + "properties": { + "nanos": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "secs": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "nanos", + "secs" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "EventMsg": { + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": "code" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the associated exec call, if available.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + } + ] + }, + "ExecCommandSource": { + "enum": [ + "agent", + "user_shell", + "unified_exec_startup", + "unified_exec_interaction" + ], + "type": "string" + }, + "ExecOutputStream": { + "enum": [ + "stdout", + "stderr" + ], + "type": "string" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "HistoryEntry": { + "properties": { + "conversation_id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "ts": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "conversation_id", + "text", + "ts" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "not_logged_in", + "bearer_token", + "o_auth" + ], + "type": "string" + }, + "McpInvocation": { + "properties": { + "arguments": { + "description": "Arguments to the tool call." + }, + "server": { + "description": "Name of the MCP server as defined in the config.", + "type": "string" + }, + "tool": { + "description": "Name of the tool as given by the MCP server.", + "type": "string" + } + }, + "required": [ + "server", + "tool" + ], + "type": "object" + }, + "McpStartupFailure": { + "properties": { + "error": { + "type": "string" + }, + "server": { + "type": "string" + } + }, + "required": [ + "error", + "server" + ], + "type": "object" + }, + "McpStartupStatus": { + "oneOf": [ + { + "properties": { + "state": { + "enum": [ + "starting" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "ready" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus2", + "type": "object" + }, + { + "properties": { + "error": { + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "error", + "state" + ], + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "cancelled" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus3", + "type": "object" + } + ] + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "PlanItemArg": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/definitions/StepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "plan_type": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resets_at": { + "description": "Unix timestamp (seconds since epoch) when the window resets.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "used_percent": { + "description": "Percentage (0-100) of the window that has been consumed.", + "format": "double", + "type": "number" + }, + "window_minutes": { + "description": "Rolling window duration, in minutes.", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "used_percent" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "RequestUserInputQuestion": { + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestionOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "RequestUserInputQuestionOption": { + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "Result_of_CallToolResult_or_String": { + "oneOf": [ + { + "properties": { + "Ok": { + "$ref": "#/definitions/CallToolResult" + } + }, + "required": [ + "Ok" + ], + "title": "OkResult_of_CallToolResult_or_String", + "type": "object" + }, + { + "properties": { + "Err": { + "type": "string" + } + }, + "required": [ + "Err" + ], + "title": "ErrResult_of_CallToolResult_or_String", + "type": "object" + } + ] + }, + "ReviewCodeLocation": { + "description": "Location of the code related to a review finding.", + "properties": { + "absolute_file_path": { + "type": "string" + }, + "line_range": { + "$ref": "#/definitions/ReviewLineRange" + } + }, + "required": [ + "absolute_file_path", + "line_range" + ], + "type": "object" + }, + "ReviewFinding": { + "description": "A single review finding describing an observed issue or recommendation.", + "properties": { + "body": { + "type": "string" + }, + "code_location": { + "$ref": "#/definitions/ReviewCodeLocation" + }, + "confidence_score": { + "format": "float", + "type": "number" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "body", + "code_location", + "confidence_score", + "priority", + "title" + ], + "type": "object" + }, + "ReviewLineRange": { + "description": "Inclusive line range in a file associated with the finding.", + "properties": { + "end": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ReviewOutputEvent": { + "description": "Structured review result produced by a child review session.", + "properties": { + "findings": { + "items": { + "$ref": "#/definitions/ReviewFinding" + }, + "type": "array" + }, + "overall_confidence_score": { + "format": "float", + "type": "number" + }, + "overall_correctness": { + "type": "string" + }, + "overall_explanation": { + "type": "string" + } + }, + "required": [ + "findings", + "overall_confidence_score", + "overall_correctness", + "overall_explanation" + ], + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions provided by the user.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brand_color": { + "type": [ + "string", + "null" + ] + }, + "default_prompt": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "icon_large": { + "type": [ + "string", + "null" + ] + }, + "icon_small": { + "type": [ + "string", + "null" + ] + }, + "short_description": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "short_description": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "StepStatus": { + "enum": [ + "pending", + "in_progress", + "completed" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byte_range": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byte_range" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "TokenUsage": { + "properties": { + "cached_input_tokens": { + "format": "int64", + "type": "integer" + }, + "input_tokens": { + "format": "int64", + "type": "integer" + }, + "output_tokens": { + "format": "int64", + "type": "integer" + }, + "reasoning_output_tokens": { + "format": "int64", + "type": "integer" + }, + "total_tokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cached_input_tokens", + "input_tokens", + "output_tokens", + "reasoning_output_tokens", + "total_tokens" + ], + "type": "object" + }, + "TokenUsageInfo": { + "properties": { + "last_token_usage": { + "$ref": "#/definitions/TokenUsage" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total_token_usage": { + "$ref": "#/definitions/TokenUsage" + } + }, + "required": [ + "last_token_usage", + "total_token_usage" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + }, + "TurnItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "UserMessage" + ], + "title": "UserMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageTurnItem", + "type": "object" + }, + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/AgentMessageContent" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "AgentMessage" + ], + "title": "AgentMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "AgentMessageTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Plan" + ], + "title": "PlanTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "raw_content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "summary_text": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "Reasoning" + ], + "title": "ReasoningTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary_text", + "type" + ], + "title": "ReasoningTurnItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "WebSearch" + ], + "title": "WebSearchTurnItemType", + "type": "string" + } + }, + "required": [ + "action", + "id", + "query", + "type" + ], + "title": "WebSearchTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "ContextCompaction" + ], + "title": "ContextCompactionTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionTurnItem", + "type": "object" + } + ] + }, + "UserInput": { + "description": "User input", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` that should be treated as special elements. These are byte ranges into the UTF-8 `text` buffer and are used to render or persist rich input markers (e.g., image placeholders) across history and resume without mutating the literal text.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "description": "Pre‑encoded data: URI image.", + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "description": "Local image path provided by the user. This will be converted to an `Image` variant (base64 data URL) during request serialization.", + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "local_image" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "description": "Skill selected by the user (name + path to SKILL.md).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "description": "Explicit mention selected by the user (name + app://connector id).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "initialMessages": { + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "type": "string" + }, + "rolloutPath": { + "type": "string" + } + }, + "required": [ + "conversationId", + "model", + "rolloutPath" + ], + "title": "ResumeConversationResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/SendUserMessageParams.json b/codex-rs/app-server-protocol/schema/json/v1/SendUserMessageParams.json new file mode 100644 index 0000000000..1f53acc006 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/SendUserMessageParams.json @@ -0,0 +1,165 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "InputItem": { + "oneOf": [ + { + "properties": { + "data": { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/V1TextElement" + }, + "type": "array" + } + }, + "required": [ + "text" + ], + "type": "object" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "TextInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "image_url": { + "type": "string" + } + }, + "required": [ + "image_url" + ], + "type": "object" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "ImageInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "LocalImageInputItem", + "type": "object" + } + ] + }, + "ThreadId": { + "type": "string" + }, + "V1ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "V1TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/V1ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + } + }, + "properties": { + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "items": { + "items": { + "$ref": "#/definitions/InputItem" + }, + "type": "array" + } + }, + "required": [ + "conversationId", + "items" + ], + "title": "SendUserMessageParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/SendUserMessageResponse.json b/codex-rs/app-server-protocol/schema/json/v1/SendUserMessageResponse.json new file mode 100644 index 0000000000..df3df37c83 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/SendUserMessageResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SendUserMessageResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/SendUserTurnParams.json b/codex-rs/app-server-protocol/schema/json/v1/SendUserTurnParams.json new file mode 100644 index 0000000000..d56ae933bd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/SendUserTurnParams.json @@ -0,0 +1,379 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "InputItem": { + "oneOf": [ + { + "properties": { + "data": { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/V1TextElement" + }, + "type": "array" + } + }, + "required": [ + "text" + ], + "type": "object" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "TextInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "image_url": { + "type": "string" + } + }, + "required": [ + "image_url" + ], + "type": "object" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "ImageInputItem", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageInputItemType", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "LocalImageInputItem", + "type": "object" + } + ] + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "ThreadId": { + "type": "string" + }, + "V1ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "V1TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/V1ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + } + }, + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "conversationId": { + "$ref": "#/definitions/ThreadId" + }, + "cwd": { + "type": "string" + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "items": { + "items": { + "$ref": "#/definitions/InputItem" + }, + "type": "array" + }, + "model": { + "type": "string" + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "sandboxPolicy": { + "$ref": "#/definitions/SandboxPolicy" + }, + "summary": { + "$ref": "#/definitions/ReasoningSummary" + } + }, + "required": [ + "approvalPolicy", + "conversationId", + "cwd", + "items", + "model", + "sandboxPolicy", + "summary" + ], + "title": "SendUserTurnParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/SendUserTurnResponse.json b/codex-rs/app-server-protocol/schema/json/v1/SendUserTurnResponse.json new file mode 100644 index 0000000000..5dc36098ff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/SendUserTurnResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SendUserTurnResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json new file mode 100644 index 0000000000..24e71bc27a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json @@ -0,0 +1,5380 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AgentMessageContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Text" + ], + "title": "TextAgentMessageContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextAgentMessageContent", + "type": "object" + } + ] + }, + "AgentStatus": { + "description": "Agent lifecycle status, derived from emitted events.", + "oneOf": [ + { + "description": "Agent is waiting for initialization.", + "enum": [ + "pending_init" + ], + "type": "string" + }, + { + "description": "Agent is currently running.", + "enum": [ + "running" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Agent is done. Contains the final assistant message.", + "properties": { + "completed": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "completed" + ], + "title": "CompletedAgentStatus", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Agent encountered an error.", + "properties": { + "errored": { + "type": "string" + } + }, + "required": [ + "errored" + ], + "title": "ErroredAgentStatus", + "type": "object" + }, + { + "description": "Agent has been shutdown.", + "enum": [ + "shutdown" + ], + "type": "string" + }, + { + "description": "Agent is not found.", + "enum": [ + "not_found" + ], + "type": "string" + } + ] + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", + "oneOf": [ + { + "description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.", + "enum": [ + "untrusted" + ], + "type": "string" + }, + { + "description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.", + "enum": [ + "on-failure" + ], + "type": "string" + }, + { + "description": "The model decides when to ask the user for approval.", + "enum": [ + "on-request" + ], + "type": "string" + }, + { + "description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.", + "enum": [ + "never" + ], + "type": "string" + } + ] + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "description": "End byte offset (exclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "description": "Start byte offset (inclusive) within the UTF-8 text buffer.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "Codex errors that we expose to clients.", + "oneOf": [ + { + "enum": [ + "context_window_exceeded", + "usage_limit_exceeded", + "internal_server_error", + "unauthorized", + "bad_request", + "sandbox_error", + "thread_rollback_failed", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "model_cap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "model_cap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "http_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "http_connection_failed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "response_stream_connection_failed": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_connection_failed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turnbefore completion.", + "properties": { + "response_stream_disconnected": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_stream_disconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "response_too_many_failed_attempts": { + "properties": { + "http_status_code": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "response_too_many_failed_attempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "has_credits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "has_credits", + "unlimited" + ], + "type": "object" + }, + "CustomPrompt": { + "properties": { + "argument_hint": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "Duration": { + "properties": { + "nanos": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "secs": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "nanos", + "secs" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "EventMsg": { + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": "code" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the associated exec call, if available.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + } + ] + }, + "ExecCommandSource": { + "enum": [ + "agent", + "user_shell", + "unified_exec_startup", + "unified_exec_interaction" + ], + "type": "string" + }, + "ExecOutputStream": { + "enum": [ + "stdout", + "stderr" + ], + "type": "string" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "HistoryEntry": { + "properties": { + "conversation_id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "ts": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "conversation_id", + "text", + "ts" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "not_logged_in", + "bearer_token", + "o_auth" + ], + "type": "string" + }, + "McpInvocation": { + "properties": { + "arguments": { + "description": "Arguments to the tool call." + }, + "server": { + "description": "Name of the MCP server as defined in the config.", + "type": "string" + }, + "tool": { + "description": "Name of the tool as given by the MCP server.", + "type": "string" + } + }, + "required": [ + "server", + "tool" + ], + "type": "object" + }, + "McpStartupFailure": { + "properties": { + "error": { + "type": "string" + }, + "server": { + "type": "string" + } + }, + "required": [ + "error", + "server" + ], + "type": "object" + }, + "McpStartupStatus": { + "oneOf": [ + { + "properties": { + "state": { + "enum": [ + "starting" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "ready" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus2", + "type": "object" + }, + { + "properties": { + "error": { + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "error", + "state" + ], + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "cancelled" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StateMcpStartupStatus3", + "type": "object" + } + ] + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "NetworkAccess": { + "description": "Represents whether outbound network access is available to the agent.", + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "PlanItemArg": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/definitions/StepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "plan_type": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resets_at": { + "description": "Unix timestamp (seconds since epoch) when the window resets.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "used_percent": { + "description": "Percentage (0-100) of the window that has been consumed.", + "format": "double", + "type": "number" + }, + "window_minutes": { + "description": "Rolling window duration, in minutes.", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "used_percent" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "RequestUserInputQuestion": { + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestionOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "RequestUserInputQuestionOption": { + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "Result_of_CallToolResult_or_String": { + "oneOf": [ + { + "properties": { + "Ok": { + "$ref": "#/definitions/CallToolResult" + } + }, + "required": [ + "Ok" + ], + "title": "OkResult_of_CallToolResult_or_String", + "type": "object" + }, + { + "properties": { + "Err": { + "type": "string" + } + }, + "required": [ + "Err" + ], + "title": "ErrResult_of_CallToolResult_or_String", + "type": "object" + } + ] + }, + "ReviewCodeLocation": { + "description": "Location of the code related to a review finding.", + "properties": { + "absolute_file_path": { + "type": "string" + }, + "line_range": { + "$ref": "#/definitions/ReviewLineRange" + } + }, + "required": [ + "absolute_file_path", + "line_range" + ], + "type": "object" + }, + "ReviewFinding": { + "description": "A single review finding describing an observed issue or recommendation.", + "properties": { + "body": { + "type": "string" + }, + "code_location": { + "$ref": "#/definitions/ReviewCodeLocation" + }, + "confidence_score": { + "format": "float", + "type": "number" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "body", + "code_location", + "confidence_score", + "priority", + "title" + ], + "type": "object" + }, + "ReviewLineRange": { + "description": "Inclusive line range in a file associated with the finding.", + "properties": { + "end": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ReviewOutputEvent": { + "description": "Structured review result produced by a child review session.", + "properties": { + "findings": { + "items": { + "$ref": "#/definitions/ReviewFinding" + }, + "type": "array" + }, + "overall_confidence_score": { + "format": "float", + "type": "number" + }, + "overall_correctness": { + "type": "string" + }, + "overall_explanation": { + "type": "string" + } + }, + "required": [ + "findings", + "overall_confidence_score", + "overall_correctness", + "overall_explanation" + ], + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions provided by the user.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "description": "Determines execution restrictions for model shell commands.", + "oneOf": [ + { + "description": "No restrictions whatsoever. Use with caution.", + "properties": { + "type": { + "enum": [ + "danger-full-access" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "description": "Read-only access to the entire file-system.", + "properties": { + "type": { + "enum": [ + "read-only" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.", + "properties": { + "network_access": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted", + "description": "Whether the external sandbox permits outbound network traffic." + }, + "type": { + "enum": [ + "external-sandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").", + "properties": { + "exclude_slash_tmp": { + "default": false, + "description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.", + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.", + "type": "boolean" + }, + "network_access": { + "default": false, + "description": "When set to `true`, outbound network access is allowed. `false` by default.", + "type": "boolean" + }, + "type": { + "enum": [ + "workspace-write" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writable_roots": { + "description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.", + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brand_color": { + "type": [ + "string", + "null" + ] + }, + "default_prompt": { + "type": [ + "string", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "icon_large": { + "type": [ + "string", + "null" + ] + }, + "icon_small": { + "type": [ + "string", + "null" + ] + }, + "short_description": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "short_description": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "StepStatus": { + "enum": [ + "pending", + "in_progress", + "completed" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byte_range": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byte_range" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "TokenUsage": { + "properties": { + "cached_input_tokens": { + "format": "int64", + "type": "integer" + }, + "input_tokens": { + "format": "int64", + "type": "integer" + }, + "output_tokens": { + "format": "int64", + "type": "integer" + }, + "reasoning_output_tokens": { + "format": "int64", + "type": "integer" + }, + "total_tokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cached_input_tokens", + "input_tokens", + "output_tokens", + "reasoning_output_tokens", + "total_tokens" + ], + "type": "object" + }, + "TokenUsageInfo": { + "properties": { + "last_token_usage": { + "$ref": "#/definitions/TokenUsage" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total_token_usage": { + "$ref": "#/definitions/TokenUsage" + } + }, + "required": [ + "last_token_usage", + "total_token_usage" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + }, + "TurnItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "UserMessage" + ], + "title": "UserMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageTurnItem", + "type": "object" + }, + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/AgentMessageContent" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "AgentMessage" + ], + "title": "AgentMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "AgentMessageTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Plan" + ], + "title": "PlanTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "raw_content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "summary_text": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "Reasoning" + ], + "title": "ReasoningTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary_text", + "type" + ], + "title": "ReasoningTurnItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "WebSearch" + ], + "title": "WebSearchTurnItemType", + "type": "string" + } + }, + "required": [ + "action", + "id", + "query", + "type" + ], + "title": "WebSearchTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "ContextCompaction" + ], + "title": "ContextCompactionTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionTurnItem", + "type": "object" + } + ] + }, + "UserInput": { + "description": "User input", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` that should be treated as special elements. These are byte ranges into the UTF-8 `text` buffer and are used to render or persist rich input markers (e.g., image placeholders) across history and resume without mutating the literal text.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "description": "Pre‑encoded data: URI image.", + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "description": "Local image path provided by the user. This will be converted to an `Image` variant (base64 data URL) during request serialization.", + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "local_image" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "description": "Skill selected by the user (name + path to SKILL.md).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "description": "Explicit mention selected by the user (name + app://connector id).", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "historyEntryCount": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "historyLogId": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initialMessages": { + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "rolloutPath": { + "type": "string" + }, + "sessionId": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "historyEntryCount", + "historyLogId", + "model", + "rolloutPath", + "sessionId" + ], + "title": "SessionConfiguredNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelParams.json b/codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelParams.json new file mode 100644 index 0000000000..3027420212 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelParams.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + } + }, + "properties": { + "model": { + "type": [ + "string", + "null" + ] + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "title": "SetDefaultModelParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelResponse.json b/codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelResponse.json new file mode 100644 index 0000000000..bb61dada65 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/SetDefaultModelResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SetDefaultModelResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v1/UserInfoResponse.json b/codex-rs/app-server-protocol/schema/json/v1/UserInfoResponse.json new file mode 100644 index 0000000000..617f6b6706 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v1/UserInfoResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "allegedUserEmail": { + "type": [ + "string", + "null" + ] + } + }, + "title": "UserInfoResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountLoginCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountLoginCompletedNotification.json new file mode 100644 index 0000000000..128cb643ab --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountLoginCompletedNotification.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "title": "AccountLoginCompletedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json new file mode 100644 index 0000000000..d168911bdf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json @@ -0,0 +1,121 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "hasCredits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "hasCredits", + "unlimited" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resetsAt": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "usedPercent": { + "format": "int32", + "type": "integer" + }, + "windowDurationMins": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "usedPercent" + ], + "type": "object" + } + }, + "properties": { + "rateLimits": { + "$ref": "#/definitions/RateLimitSnapshot" + } + }, + "required": [ + "rateLimits" + ], + "title": "AccountRateLimitsUpdatedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json new file mode 100644 index 0000000000..d95d737060 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "enum": [ + "apikey" + ], + "type": "string" + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "enum": [ + "chatgpt" + ], + "type": "string" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "enum": [ + "chatgptAuthTokens" + ], + "type": "string" + } + ] + } + }, + "properties": { + "authMode": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + } + }, + "title": "AccountUpdatedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/AgentMessageDeltaNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AgentMessageDeltaNotification.json new file mode 100644 index 0000000000..09510d95cf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/AgentMessageDeltaNotification.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "AgentMessageDeltaNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/AppsListParams.json b/codex-rs/app-server-protocol/schema/json/v2/AppsListParams.json new file mode 100644 index 0000000000..3625f7b30b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/AppsListParams.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "AppsListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json new file mode 100644 index 0000000000..f5cac70771 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AppInfo": { + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "distributionChannel": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "installUrl": { + "type": [ + "string", + "null" + ] + }, + "isAccessible": { + "default": false, + "type": "boolean" + }, + "logoUrl": { + "type": [ + "string", + "null" + ] + }, + "logoUrlDark": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/AppInfo" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "AppsListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountParams.json b/codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountParams.json new file mode 100644 index 0000000000..22c9a2ac3c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "loginId": { + "type": "string" + } + }, + "required": [ + "loginId" + ], + "title": "CancelLoginAccountParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountResponse.json new file mode 100644 index 0000000000..23df186da4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/CancelLoginAccountResponse.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CancelLoginAccountStatus": { + "enum": [ + "canceled", + "notFound" + ], + "type": "string" + } + }, + "properties": { + "status": { + "$ref": "#/definitions/CancelLoginAccountStatus" + } + }, + "required": [ + "status" + ], + "title": "CancelLoginAccountResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json b/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json new file mode 100644 index 0000000000..bbac0d4f10 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - list collaboration mode presets.", + "title": "CollaborationModeListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json new file mode 100644 index 0000000000..7da8fbaa47 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CollaborationModeMask": { + "description": "A mask for collaboration mode settings, allowing partial updates. All fields except `name` are optional, enabling selective updates.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "mode": { + "anyOf": [ + { + "$ref": "#/definitions/ModeKind" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + } + }, + "description": "EXPERIMENTAL - collaboration mode presets response.", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/CollaborationModeMask" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "CollaborationModeListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json b/codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json new file mode 100644 index 0000000000..6dd8fb7bc8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json @@ -0,0 +1,147 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + } + }, + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "timeoutMs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "command" + ], + "title": "CommandExecParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CommandExecResponse.json b/codex-rs/app-server-protocol/schema/json/v2/CommandExecResponse.json new file mode 100644 index 0000000000..8ca0f46b77 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/CommandExecResponse.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "exitCode": { + "format": "int32", + "type": "integer" + }, + "stderr": { + "type": "string" + }, + "stdout": { + "type": "string" + } + }, + "required": [ + "exitCode", + "stderr", + "stdout" + ], + "title": "CommandExecResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CommandExecutionOutputDeltaNotification.json b/codex-rs/app-server-protocol/schema/json/v2/CommandExecutionOutputDeltaNotification.json new file mode 100644 index 0000000000..e4cb64a9dd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/CommandExecutionOutputDeltaNotification.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "CommandExecutionOutputDeltaNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigBatchWriteParams.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigBatchWriteParams.json new file mode 100644 index 0000000000..37b23b2405 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigBatchWriteParams.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ConfigEdit": { + "properties": { + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "type": "object" + }, + "MergeStrategy": { + "enum": [ + "replace", + "upsert" + ], + "type": "string" + } + }, + "properties": { + "edits": { + "items": { + "$ref": "#/definitions/ConfigEdit" + }, + "type": "array" + }, + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "edits" + ], + "title": "ConfigBatchWriteParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigReadParams.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadParams.json new file mode 100644 index 0000000000..b173d2ba95 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadParams.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwd": { + "description": "Optional working directory to resolve project config layers. If specified, return the effective config as seen from that directory (i.e., including any project layers between `cwd` and the project/repo root).", + "type": [ + "string", + "null" + ] + }, + "includeLayers": { + "default": false, + "type": "boolean" + } + }, + "title": "ConfigReadParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json new file mode 100644 index 0000000000..96ce16be09 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json @@ -0,0 +1,604 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AnalyticsConfig": { + "additionalProperties": true, + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "Config": { + "additionalProperties": true, + "properties": { + "analytics": { + "anyOf": [ + { + "$ref": "#/definitions/AnalyticsConfig" + }, + { + "type": "null" + } + ] + }, + "approval_policy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "compact_prompt": { + "type": [ + "string", + "null" + ] + }, + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "forced_chatgpt_workspace_id": { + "type": [ + "string", + "null" + ] + }, + "forced_login_method": { + "anyOf": [ + { + "$ref": "#/definitions/ForcedLoginMethod" + }, + { + "type": "null" + } + ] + }, + "instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "model_auto_compact_token_limit": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "model_provider": { + "type": [ + "string", + "null" + ] + }, + "model_reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "model_reasoning_summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "model_verbosity": { + "anyOf": [ + { + "$ref": "#/definitions/Verbosity" + }, + { + "type": "null" + } + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "profiles": { + "additionalProperties": { + "$ref": "#/definitions/ProfileV2" + }, + "default": {}, + "type": "object" + }, + "review_model": { + "type": [ + "string", + "null" + ] + }, + "sandbox_mode": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "sandbox_workspace_write": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxWorkspaceWrite" + }, + { + "type": "null" + } + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/ToolsV2" + }, + { + "type": "null" + } + ] + }, + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ConfigLayer": { + "properties": { + "config": true, + "disabledReason": { + "type": [ + "string", + "null" + ] + }, + "name": { + "$ref": "#/definitions/ConfigLayerSource" + }, + "version": { + "type": "string" + } + }, + "required": [ + "config", + "name", + "version" + ], + "type": "object" + }, + "ConfigLayerMetadata": { + "properties": { + "name": { + "$ref": "#/definitions/ConfigLayerSource" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "ConfigLayerSource": { + "oneOf": [ + { + "description": "Managed preferences layer delivered by MDM (macOS only).", + "properties": { + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "enum": [ + "mdm" + ], + "title": "MdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "domain", + "key", + "type" + ], + "title": "MdmConfigLayerSource", + "type": "object" + }, + { + "description": "Managed config layer from a file (usually `managed_config.toml`).", + "properties": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "This is the path to the system config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "system" + ], + "title": "SystemConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "SystemConfigLayerSource", + "type": "object" + }, + { + "description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", + "properties": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "This is the path to the user's config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "user" + ], + "title": "UserConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "UserConfigLayerSource", + "type": "object" + }, + { + "description": "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + "properties": { + "dotCodexFolder": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "project" + ], + "title": "ProjectConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "dotCodexFolder", + "type" + ], + "title": "ProjectConfigLayerSource", + "type": "object" + }, + { + "description": "Session-layer overrides supplied via `-c`/`--config`.", + "properties": { + "type": { + "enum": [ + "sessionFlags" + ], + "title": "SessionFlagsConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SessionFlagsConfigLayerSource", + "type": "object" + }, + { + "description": "`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a \"best effort\" while we phase out `managed_config.toml` in favor of `requirements.toml`.", + "properties": { + "file": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "legacyManagedConfigTomlFromFile" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSource", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "legacyManagedConfigTomlFromMdm" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSource", + "type": "object" + } + ] + }, + "ForcedLoginMethod": { + "enum": [ + "chatgpt", + "api" + ], + "type": "string" + }, + "ProfileV2": { + "additionalProperties": true, + "properties": { + "approval_policy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "chatgpt_base_url": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "model_provider": { + "type": [ + "string", + "null" + ] + }, + "model_reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "model_reasoning_summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "model_verbosity": { + "anyOf": [ + { + "$ref": "#/definitions/Verbosity" + }, + { + "type": "null" + } + ] + }, + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "SandboxWorkspaceWrite": { + "properties": { + "exclude_slash_tmp": { + "default": false, + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "type": "boolean" + }, + "network_access": { + "default": false, + "type": "boolean" + }, + "writable_roots": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "ToolsV2": { + "properties": { + "view_image": { + "type": [ + "boolean", + "null" + ] + }, + "web_search": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "Verbosity": { + "description": "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "low", + "medium", + "high" + ], + "type": "string" + }, + "WebSearchMode": { + "enum": [ + "disabled", + "cached", + "live" + ], + "type": "string" + } + }, + "properties": { + "config": { + "$ref": "#/definitions/Config" + }, + "layers": { + "items": { + "$ref": "#/definitions/ConfigLayer" + }, + "type": [ + "array", + "null" + ] + }, + "origins": { + "additionalProperties": { + "$ref": "#/definitions/ConfigLayerMetadata" + }, + "type": "object" + } + }, + "required": [ + "config", + "origins" + ], + "title": "ConfigReadResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json new file mode 100644 index 0000000000..9e77238b75 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "ConfigRequirements": { + "properties": { + "allowedApprovalPolicies": { + "items": { + "$ref": "#/definitions/AskForApproval" + }, + "type": [ + "array", + "null" + ] + }, + "allowedSandboxModes": { + "items": { + "$ref": "#/definitions/SandboxMode" + }, + "type": [ + "array", + "null" + ] + }, + "enforceResidency": { + "anyOf": [ + { + "$ref": "#/definitions/ResidencyRequirement" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ResidencyRequirement": { + "enum": [ + "us" + ], + "type": "string" + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + } + }, + "properties": { + "requirements": { + "anyOf": [ + { + "$ref": "#/definitions/ConfigRequirements" + }, + { + "type": "null" + } + ], + "description": "Null if no requirements are configured (e.g. no requirements.toml/MDM entries)." + } + }, + "title": "ConfigRequirementsReadResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigValueWriteParams.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigValueWriteParams.json new file mode 100644 index 0000000000..000c55a830 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigValueWriteParams.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MergeStrategy": { + "enum": [ + "replace", + "upsert" + ], + "type": "string" + } + }, + "properties": { + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + }, + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "title": "ConfigValueWriteParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigWarningNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigWarningNotification.json new file mode 100644 index 0000000000..c89e42a2b4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigWarningNotification.json @@ -0,0 +1,77 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "TextPosition": { + "properties": { + "column": { + "description": "1-based column number (in Unicode scalar values).", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "line": { + "description": "1-based line number.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "column", + "line" + ], + "type": "object" + }, + "TextRange": { + "properties": { + "end": { + "$ref": "#/definitions/TextPosition" + }, + "start": { + "$ref": "#/definitions/TextPosition" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + } + }, + "properties": { + "details": { + "description": "Optional extra guidance or error details.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Optional path to the config file that triggered the warning.", + "type": [ + "string", + "null" + ] + }, + "range": { + "anyOf": [ + { + "$ref": "#/definitions/TextRange" + }, + { + "type": "null" + } + ], + "description": "Optional range for the error location inside the config file." + }, + "summary": { + "description": "Concise summary of the warning.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "title": "ConfigWarningNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json new file mode 100644 index 0000000000..631318a8bd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json @@ -0,0 +1,237 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "ConfigLayerMetadata": { + "properties": { + "name": { + "$ref": "#/definitions/ConfigLayerSource" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "ConfigLayerSource": { + "oneOf": [ + { + "description": "Managed preferences layer delivered by MDM (macOS only).", + "properties": { + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "enum": [ + "mdm" + ], + "title": "MdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "domain", + "key", + "type" + ], + "title": "MdmConfigLayerSource", + "type": "object" + }, + { + "description": "Managed config layer from a file (usually `managed_config.toml`).", + "properties": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "This is the path to the system config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "system" + ], + "title": "SystemConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "SystemConfigLayerSource", + "type": "object" + }, + { + "description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", + "properties": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "This is the path to the user's config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "user" + ], + "title": "UserConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "UserConfigLayerSource", + "type": "object" + }, + { + "description": "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + "properties": { + "dotCodexFolder": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "project" + ], + "title": "ProjectConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "dotCodexFolder", + "type" + ], + "title": "ProjectConfigLayerSource", + "type": "object" + }, + { + "description": "Session-layer overrides supplied via `-c`/`--config`.", + "properties": { + "type": { + "enum": [ + "sessionFlags" + ], + "title": "SessionFlagsConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SessionFlagsConfigLayerSource", + "type": "object" + }, + { + "description": "`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a \"best effort\" while we phase out `managed_config.toml` in favor of `requirements.toml`.", + "properties": { + "file": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "legacyManagedConfigTomlFromFile" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSource", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "legacyManagedConfigTomlFromMdm" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSource", + "type": "object" + } + ] + }, + "OverriddenMetadata": { + "properties": { + "effectiveValue": true, + "message": { + "type": "string" + }, + "overridingLayer": { + "$ref": "#/definitions/ConfigLayerMetadata" + } + }, + "required": [ + "effectiveValue", + "message", + "overridingLayer" + ], + "type": "object" + }, + "WriteStatus": { + "enum": [ + "ok", + "okOverridden" + ], + "type": "string" + } + }, + "properties": { + "filePath": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "Canonical path to the config file that was written." + }, + "overriddenMetadata": { + "anyOf": [ + { + "$ref": "#/definitions/OverriddenMetadata" + }, + { + "type": "null" + } + ] + }, + "status": { + "$ref": "#/definitions/WriteStatus" + }, + "version": { + "type": "string" + } + }, + "required": [ + "filePath", + "status", + "version" + ], + "title": "ConfigWriteResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ContextCompactedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ContextCompactedNotification.json new file mode 100644 index 0000000000..8d2d4b1261 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ContextCompactedNotification.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "title": "ContextCompactedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeprecationNoticeNotification.json b/codex-rs/app-server-protocol/schema/json/v2/DeprecationNoticeNotification.json new file mode 100644 index 0000000000..7e6c73b9c7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeprecationNoticeNotification.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "title": "DeprecationNoticeNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json new file mode 100644 index 0000000000..17991ebaed --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json @@ -0,0 +1,197 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + } + }, + "properties": { + "error": { + "$ref": "#/definitions/TurnError" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "willRetry": { + "type": "boolean" + } + }, + "required": [ + "error", + "threadId", + "turnId", + "willRetry" + ], + "title": "ErrorNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json b/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json new file mode 100644 index 0000000000..e4171b27f4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "classification": { + "type": "string" + }, + "includeLogs": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "classification", + "includeLogs" + ], + "title": "FeedbackUploadParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadResponse.json new file mode 100644 index 0000000000..647b613f0b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "FeedbackUploadResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/FileChangeOutputDeltaNotification.json b/codex-rs/app-server-protocol/schema/json/v2/FileChangeOutputDeltaNotification.json new file mode 100644 index 0000000000..2b3abd67f9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/FileChangeOutputDeltaNotification.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "FileChangeOutputDeltaNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountParams.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountParams.json new file mode 100644 index 0000000000..ca18a451e9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountParams.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "refreshToken": { + "default": false, + "description": "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.", + "type": "boolean" + } + }, + "title": "GetAccountParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json new file mode 100644 index 0000000000..a7025fbaea --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json @@ -0,0 +1,121 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "hasCredits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "hasCredits", + "unlimited" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resetsAt": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "usedPercent": { + "format": "int32", + "type": "integer" + }, + "windowDurationMins": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "usedPercent" + ], + "type": "object" + } + }, + "properties": { + "rateLimits": { + "$ref": "#/definitions/RateLimitSnapshot" + } + }, + "required": [ + "rateLimits" + ], + "title": "GetAccountRateLimitsResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json new file mode 100644 index 0000000000..6646bd8c97 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Account": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyAccountType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ApiKeyAccount", + "type": "object" + }, + { + "properties": { + "email": { + "type": "string" + }, + "planType": { + "$ref": "#/definitions/PlanType" + }, + "type": { + "enum": [ + "chatgpt" + ], + "title": "ChatgptAccountType", + "type": "string" + } + }, + "required": [ + "email", + "planType", + "type" + ], + "title": "ChatgptAccount", + "type": "object" + } + ] + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + } + }, + "properties": { + "account": { + "anyOf": [ + { + "$ref": "#/definitions/Account" + }, + { + "type": "null" + } + ] + }, + "requiresOpenaiAuth": { + "type": "boolean" + } + }, + "required": [ + "requiresOpenaiAuth" + ], + "title": "GetAccountResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json new file mode 100644 index 0000000000..866ce9aa2b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -0,0 +1,1316 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "item": { + "$ref": "#/definitions/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "ItemCompletedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json new file mode 100644 index 0000000000..2fd71404f6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -0,0 +1,1316 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "item": { + "$ref": "#/definitions/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "ItemStartedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusParams.json b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusParams.json new file mode 100644 index 0000000000..e78dbeac16 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusParams.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a server-defined value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ListMcpServerStatusParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json new file mode 100644 index 0000000000..2ee4926965 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json @@ -0,0 +1,325 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "notLoggedIn", + "bearerToken", + "oAuth" + ], + "type": "string" + }, + "McpServerStatus": { + "properties": { + "authStatus": { + "$ref": "#/definitions/McpAuthStatus" + }, + "name": { + "type": "string" + }, + "resourceTemplates": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "resources": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "type": "object" + } + }, + "required": [ + "authStatus", + "name", + "resourceTemplates", + "resources", + "tools" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "$ref": "#/definitions/ToolInputSchema" + }, + "name": { + "type": "string" + }, + "outputSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ToolOutputSchema" + }, + { + "type": "null" + } + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", + "properties": { + "destructiveHint": { + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "type": [ + "boolean", + "null" + ] + }, + "readOnlyHint": { + "type": [ + "boolean", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ToolInputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + }, + "ToolOutputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", + "properties": { + "properties": true, + "required": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "default": "object", + "type": "string" + } + }, + "type": "object" + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/McpServerStatus" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ListMcpServerStatusResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json new file mode 100644 index 0000000000..66df09435b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "apiKey": { + "type": "string" + }, + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "apiKey", + "type" + ], + "title": "ApiKeyv2::LoginAccountParams", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "Chatgptv2::LoginAccountParams", + "type": "object" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", + "properties": { + "accessToken": { + "description": "Access token (JWT) supplied by the client. This token is used for backend API requests.", + "type": "string" + }, + "idToken": { + "description": "ID token (JWT) supplied by the client.\n\nThis token is used for identity and account metadata (email, plan type, workspace id).", + "type": "string" + }, + "type": { + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "accessToken", + "idToken", + "type" + ], + "title": "ChatgptAuthTokensv2::LoginAccountParams", + "type": "object" + } + ], + "title": "LoginAccountParams" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json new file mode 100644 index 0000000000..e2697ea44e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ApiKeyv2::LoginAccountResponse", + "type": "object" + }, + { + "properties": { + "authUrl": { + "description": "URL the client should open in a browser to initiate the OAuth flow.", + "type": "string" + }, + "loginId": { + "type": "string" + }, + "type": { + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "authUrl", + "loginId", + "type" + ], + "title": "Chatgptv2::LoginAccountResponse", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptAuthTokensv2::LoginAccountResponse", + "type": "object" + } + ], + "title": "LoginAccountResponse" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/LogoutAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/LogoutAccountResponse.json new file mode 100644 index 0000000000..56415a0311 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/LogoutAccountResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LogoutAccountResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginCompletedNotification.json new file mode 100644 index 0000000000..35efd2baf2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginCompletedNotification.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "name", + "success" + ], + "title": "McpServerOauthLoginCompletedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginParams.json b/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginParams.json new file mode 100644 index 0000000000..4370f444b9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginParams.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "name": { + "type": "string" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "timeoutSecs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "name" + ], + "title": "McpServerOauthLoginParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginResponse.json b/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginResponse.json new file mode 100644 index 0000000000..efeb612dd3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/McpServerOauthLoginResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authorizationUrl": { + "type": "string" + } + }, + "required": [ + "authorizationUrl" + ], + "title": "McpServerOauthLoginResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/McpServerRefreshResponse.json b/codex-rs/app-server-protocol/schema/json/v2/McpServerRefreshResponse.json new file mode 100644 index 0000000000..779192e779 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/McpServerRefreshResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerRefreshResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/McpToolCallProgressNotification.json b/codex-rs/app-server-protocol/schema/json/v2/McpToolCallProgressNotification.json new file mode 100644 index 0000000000..419cab74a3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/McpToolCallProgressNotification.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "message": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "message", + "threadId", + "turnId" + ], + "title": "McpToolCallProgressNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ModelListParams.json b/codex-rs/app-server-protocol/schema/json/v2/ModelListParams.json new file mode 100644 index 0000000000..13bb29977e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ModelListParams.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ModelListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json new file mode 100644 index 0000000000..d41f13a87d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Model": { + "properties": { + "defaultReasoningEffort": { + "$ref": "#/definitions/ReasoningEffort" + }, + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDefault": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "supportedReasoningEfforts": { + "items": { + "$ref": "#/definitions/ReasoningEffortOption" + }, + "type": "array" + }, + "supportsPersonality": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "defaultReasoningEffort", + "description", + "displayName", + "id", + "isDefault", + "model", + "supportedReasoningEfforts" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningEffortOption": { + "properties": { + "description": { + "type": "string" + }, + "reasoningEffort": { + "$ref": "#/definitions/ReasoningEffort" + } + }, + "required": [ + "description", + "reasoningEffort" + ], + "type": "object" + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/Model" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ModelListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/PlanDeltaNotification.json b/codex-rs/app-server-protocol/schema/json/v2/PlanDeltaNotification.json new file mode 100644 index 0000000000..6446392626 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/PlanDeltaNotification.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "PlanDeltaNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json new file mode 100644 index 0000000000..47039df05b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json @@ -0,0 +1,770 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "RawResponseItemCompletedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryPartAddedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryPartAddedNotification.json new file mode 100644 index 0000000000..33debf2a2e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryPartAddedNotification.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "title": "ReasoningSummaryPartAddedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryTextDeltaNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryTextDeltaNotification.json new file mode 100644 index 0000000000..6f50a8403a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ReasoningSummaryTextDeltaNotification.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "title": "ReasoningSummaryTextDeltaNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReasoningTextDeltaNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ReasoningTextDeltaNotification.json new file mode 100644 index 0000000000..ebfd5dc854 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ReasoningTextDeltaNotification.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "contentIndex": { + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "contentIndex", + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "ReasoningTextDeltaNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartParams.json new file mode 100644 index 0000000000..0089d46491 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartParams.json @@ -0,0 +1,129 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ReviewDelivery": { + "enum": [ + "inline", + "detached" + ], + "type": "string" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions, equivalent to the old free-form prompt.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + } + }, + "properties": { + "delivery": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewDelivery" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`)." + }, + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "target", + "threadId" + ], + "title": "ReviewStartParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json new file mode 100644 index 0000000000..341ff78c0a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -0,0 +1,1526 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "reviewThreadId": { + "description": "Identifies the thread where the review runs.\n\nFor inline reviews, this is the original thread id. For detached reviews, this is the id of the new review thread.", + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "reviewThreadId", + "turn" + ], + "title": "ReviewStartResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteParams.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteParams.json new file mode 100644 index 0000000000..3fa74811d5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteParams.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "enabled": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "enabled", + "path" + ], + "title": "SkillsConfigWriteParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteResponse.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteResponse.json new file mode 100644 index 0000000000..09d73b44c3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsConfigWriteResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "effectiveEnabled": { + "type": "boolean" + } + }, + "required": [ + "effectiveEnabled" + ], + "title": "SkillsConfigWriteResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsListParams.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsListParams.json new file mode 100644 index 0000000000..a9a8a9ef8d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsListParams.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwds": { + "description": "When empty, defaults to the current session working directory.", + "items": { + "type": "string" + }, + "type": "array" + }, + "forceReload": { + "description": "When true, bypass the skills cache and re-scan skills from disk.", + "type": "boolean" + } + }, + "title": "SkillsListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json new file mode 100644 index 0000000000..b4ec51ba78 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json @@ -0,0 +1,215 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brandColor": { + "type": [ + "string", + "null" + ] + }, + "defaultPrompt": { + "type": [ + "string", + "null" + ] + }, + "displayName": { + "type": [ + "string", + "null" + ] + }, + "iconLarge": { + "type": [ + "string", + "null" + ] + }, + "iconSmall": { + "type": [ + "string", + "null" + ] + }, + "shortDescription": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "shortDescription": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "SkillsListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TerminalInteractionNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TerminalInteractionNotification.json new file mode 100644 index 0000000000..ca2648a313 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TerminalInteractionNotification.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "processId": { + "type": "string" + }, + "stdin": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "processId", + "stdin", + "threadId", + "turnId" + ], + "title": "TerminalInteractionNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveParams.json new file mode 100644 index 0000000000..49322b60a4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadArchiveParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveResponse.json new file mode 100644 index 0000000000..bfd853e59e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadArchiveResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadArchiveResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json new file mode 100644 index 0000000000..1de59e2a09 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json @@ -0,0 +1,98 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + } + }, + "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the forked thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Specify the rollout path to fork from. If specified, the thread_id param will be ignored.", + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadForkParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json new file mode 100644 index 0000000000..0a3826d8fd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -0,0 +1,1859 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadForkResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json new file mode 100644 index 0000000000..dd4c7a4f12 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json @@ -0,0 +1,85 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ThreadSortKey": { + "enum": [ + "created_at", + "updated_at" + ], + "type": "string" + }, + "ThreadSourceKind": { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "subAgent", + "subAgentReview", + "subAgentCompact", + "subAgentThreadSpawn", + "subAgentOther", + "unknown" + ], + "type": "string" + } + }, + "properties": { + "archived": { + "description": "Optional archived filter; when set to true, only archived threads are returned. If false or null, only non-archived threads are returned.", + "type": [ + "boolean", + "null" + ] + }, + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "modelProviders": { + "description": "Optional provider filter; when set, only sessions recorded under these providers are returned. When present but empty, includes all providers.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "sortKey": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadSortKey" + }, + { + "type": "null" + } + ], + "description": "Optional sort key; defaults to created_at." + }, + "sourceKinds": { + "description": "Optional source filter; when set, only sessions from these source kinds are returned. When omitted or empty, defaults to interactive sources.", + "items": { + "$ref": "#/definitions/ThreadSourceKind" + }, + "type": [ + "array", + "null" + ] + } + }, + "title": "ThreadListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json new file mode 100644 index 0000000000..eb4ec81f68 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -0,0 +1,1712 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/Thread" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ThreadListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListParams.json new file mode 100644 index 0000000000..d10ee7ed96 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListParams.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to no limit.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ThreadLoadedListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListResponse.json new file mode 100644 index 0000000000..cfd90fb813 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadLoadedListResponse.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "description": "Thread ids for sessions currently loaded in memory.", + "items": { + "type": "string" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ThreadLoadedListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadNameUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadNameUpdatedNotification.json new file mode 100644 index 0000000000..8c3b2095f5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadNameUpdatedNotification.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "threadName": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "threadId" + ], + "title": "ThreadNameUpdatedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadParams.json new file mode 100644 index 0000000000..f5e5503cc0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadParams.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "includeTurns": { + "default": false, + "description": "When true, include turns and their items from rollout history.", + "type": "boolean" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadReadParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json new file mode 100644 index 0000000000..02dab752e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -0,0 +1,1702 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadReadResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json new file mode 100644 index 0000000000..3da1e3c749 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -0,0 +1,872 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses/Chat Completions APIs understand.", + "properties": { + "content": { + "type": "string" + }, + "content_items": { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "Personality": { + "enum": [ + "friendly", + "pragmatic" + ], + "type": "string" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Set when using the chat completions API.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "type": "string" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "history": { + "description": "[UNSTABLE] FOR CODEX CLOUD - DO NOT USE. If specified, the thread will be resumed with the provided history instead of loaded from disk.", + "items": { + "$ref": "#/definitions/ResponseItem" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the resumed thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Specify the rollout path to resume from. If specified, the thread_id param will be ignored.", + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadResumeParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json new file mode 100644 index 0000000000..dad4ccbfeb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -0,0 +1,1859 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadResumeResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackParams.json new file mode 100644 index 0000000000..cb3ba0db39 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackParams.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "numTurns": { + "description": "The number of turns to drop from the end of the thread. Must be >= 1.\n\nThis only modifies the thread's history and does not revert local file changes that have been made by the agent. Clients are responsible for reverting these changes.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "numTurns", + "threadId" + ], + "title": "ThreadRollbackParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json new file mode 100644 index 0000000000..3ebb5052d3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -0,0 +1,1707 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "thread": { + "allOf": [ + { + "$ref": "#/definitions/Thread" + } + ], + "description": "The updated thread after applying the rollback, with `turns` populated.\n\nThe ThreadItems stored in each Turn are lossy since we explicitly do not persist all agent interactions, such as command executions. This is the same behavior as `thread/resume`." + } + }, + "required": [ + "thread" + ], + "title": "ThreadRollbackResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameParams.json new file mode 100644 index 0000000000..9381c7cb12 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameParams.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "name": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "name", + "threadId" + ], + "title": "ThreadSetNameParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameResponse.json new file mode 100644 index 0000000000..3d25712ff0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadSetNameResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadSetNameResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json new file mode 100644 index 0000000000..d002d03a19 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -0,0 +1,137 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "DynamicToolSpec": { + "properties": { + "description": { + "type": "string" + }, + "inputSchema": true, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "inputSchema", + "name" + ], + "type": "object" + }, + "Personality": { + "enum": [ + "friendly", + "pragmatic" + ], + "type": "string" + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + } + }, + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "dynamicTools": { + "items": { + "$ref": "#/definitions/DynamicToolSpec" + }, + "type": [ + "array", + "null" + ] + }, + "ephemeral": { + "type": [ + "boolean", + "null" + ] + }, + "experimentalRawEvents": { + "default": false, + "description": "If true, opt into emitting raw response items on the event stream.\n\nThis is for internal use only (e.g. Codex Cloud). (TODO): Figure out a better way to categorize internal / experimental events & protocols.", + "type": "boolean" + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + } + }, + "title": "ThreadStartParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json new file mode 100644 index 0000000000..d88d3694de --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -0,0 +1,1859 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadStartResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json new file mode 100644 index 0000000000..0c8d47136e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -0,0 +1,1702 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadStartedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadTokenUsageUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadTokenUsageUpdatedNotification.json new file mode 100644 index 0000000000..111de85c62 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadTokenUsageUpdatedNotification.json @@ -0,0 +1,77 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ThreadTokenUsage": { + "properties": { + "last": { + "$ref": "#/definitions/TokenUsageBreakdown" + }, + "modelContextWindow": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total": { + "$ref": "#/definitions/TokenUsageBreakdown" + } + }, + "required": [ + "last", + "total" + ], + "type": "object" + }, + "TokenUsageBreakdown": { + "properties": { + "cachedInputTokens": { + "format": "int64", + "type": "integer" + }, + "inputTokens": { + "format": "int64", + "type": "integer" + }, + "outputTokens": { + "format": "int64", + "type": "integer" + }, + "reasoningOutputTokens": { + "format": "int64", + "type": "integer" + }, + "totalTokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cachedInputTokens", + "inputTokens", + "outputTokens", + "reasoningOutputTokens", + "totalTokens" + ], + "type": "object" + } + }, + "properties": { + "threadId": { + "type": "string" + }, + "tokenUsage": { + "$ref": "#/definitions/ThreadTokenUsage" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "tokenUsage", + "turnId" + ], + "title": "ThreadTokenUsageUpdatedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveParams.json new file mode 100644 index 0000000000..fd62a96cc3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadUnarchiveParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json new file mode 100644 index 0000000000..46478554bb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -0,0 +1,1702 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Thread": { + "properties": { + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "id", + "modelProvider", + "preview", + "source", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadUnarchiveResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json new file mode 100644 index 0000000000..918f82a456 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -0,0 +1,1525 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "title": "TurnCompletedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnDiffUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnDiffUpdatedNotification.json new file mode 100644 index 0000000000..b694ce254c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnDiffUpdatedNotification.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Notification that the turn-level unified diff has changed. Contains the latest aggregated diff across all file changes in the turn.", + "properties": { + "diff": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "diff", + "threadId", + "turnId" + ], + "title": "TurnDiffUpdatedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnInterruptParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnInterruptParams.json new file mode 100644 index 0000000000..9181428a10 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnInterruptParams.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "title": "TurnInterruptParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnInterruptResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnInterruptResponse.json new file mode 100644 index 0000000000..5d8a0f9ce2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnInterruptResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnInterruptResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnPlanUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnPlanUpdatedNotification.json new file mode 100644 index 0000000000..5a28ffbf17 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnPlanUpdatedNotification.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "TurnPlanStep": { + "properties": { + "status": { + "$ref": "#/definitions/TurnPlanStepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "TurnPlanStepStatus": { + "enum": [ + "pending", + "inProgress", + "completed" + ], + "type": "string" + } + }, + "properties": { + "explanation": { + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/TurnPlanStep" + }, + "type": "array" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "plan", + "threadId", + "turnId" + ], + "title": "TurnPlanUpdatedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json new file mode 100644 index 0000000000..35a87890ae --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -0,0 +1,476 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "AskForApproval": { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "code", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "Personality": { + "enum": [ + "friendly", + "pragmatic" + ], + "type": "string" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + } + }, + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "collaborationMode": { + "anyOf": [ + { + "$ref": "#/definitions/CollaborationMode" + }, + { + "type": "null" + } + ], + "description": "EXPERIMENTAL - set a pre-set collaboration mode. Takes precedence over model, reasoning_effort, and developer instructions if set." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "title": "TurnStartParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json new file mode 100644 index 0000000000..d0d09a3f20 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -0,0 +1,1521 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "turn" + ], + "title": "TurnStartResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json new file mode 100644 index 0000000000..9dd23a33e6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -0,0 +1,1525 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "items": { + "$ref": "#/definitions/Role" + }, + "type": [ + "array", + "null" + ] + }, + "lastModified": { + "type": [ + "string", + "null" + ] + }, + "priority": { + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "modelCap": { + "properties": { + "model": { + "type": "string" + }, + "reset_after_seconds": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "modelCap" + ], + "title": "ModelCapCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/ResourceLink" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "resource": { + "$ref": "#/definitions/EmbeddedResourceResource" + }, + "type": { + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmbeddedResourceResource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentBlock" + }, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "anyOf": [ + { + "$ref": "#/definitions/Annotations" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + } + }, + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "title": "TurnStartedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/WindowsWorldWritableWarningNotification.json b/codex-rs/app-server-protocol/schema/json/v2/WindowsWorldWritableWarningNotification.json new file mode 100644 index 0000000000..893dbbaf10 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/WindowsWorldWritableWarningNotification.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "extraCount": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "failedScan": { + "type": "boolean" + }, + "samplePaths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "extraCount", + "failedScan", + "samplePaths" + ], + "title": "WindowsWorldWritableWarningNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/AbsolutePathBuf.ts b/codex-rs/app-server-protocol/schema/typescript/AbsolutePathBuf.ts new file mode 100644 index 0000000000..dc1cde1241 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AbsolutePathBuf.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * A path that is guaranteed to be absolute and normalized (though it is not + * guaranteed to be canonicalized or exist on the filesystem). + * + * IMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set + * using [AbsolutePathBufGuard::new]. If no base path is set, the + * deserialization will fail unless the path being deserialized is already + * absolute. + */ +export type AbsolutePathBuf = string; diff --git a/codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts b/codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts new file mode 100644 index 0000000000..6441bed68a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type AddConversationListenerParams = { conversationId: ThreadId, experimentalRawEvents: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts b/codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts new file mode 100644 index 0000000000..f7e34ef658 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AddConversationSubscriptionResponse = { subscriptionId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts new file mode 100644 index 0000000000..dc2cfb77e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageContent = { "type": "Text", text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts new file mode 100644 index 0000000000..1473a4f2bc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts new file mode 100644 index 0000000000..1e12d85fbb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts new file mode 100644 index 0000000000..ee436566e0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageEvent = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts new file mode 100644 index 0000000000..f884067581 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentMessageContent } from "./AgentMessageContent"; + +export type AgentMessageItem = { id: string, content: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts new file mode 100644 index 0000000000..fc2c221937 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts new file mode 100644 index 0000000000..bf0062cd43 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningEvent = { text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts new file mode 100644 index 0000000000..fcfa816f5d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningRawContentDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts new file mode 100644 index 0000000000..364c278229 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningRawContentEvent = { text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts new file mode 100644 index 0000000000..604aceed93 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningSectionBreakEvent = { item_id: string, summary_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts new file mode 100644 index 0000000000..ddf6789c78 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Agent lifecycle status, derived from emitted events. + */ +export type AgentStatus = "pending_init" | "running" | { "completed": string | null } | { "errored": string } | "shutdown" | "not_found"; diff --git a/codex-rs/app-server-protocol/schema/typescript/Annotations.ts b/codex-rs/app-server-protocol/schema/typescript/Annotations.ts new file mode 100644 index 0000000000..b89cbd725f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Annotations.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Role } from "./Role"; + +/** + * Optional annotations for the client. The client can use annotations to inform how objects are used or displayed + */ +export type Annotations = { audience?: Array, lastModified?: string, priority?: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalParams.ts b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalParams.ts new file mode 100644 index 0000000000..27de027cc6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalParams.ts @@ -0,0 +1,21 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChange } from "./FileChange"; +import type { ThreadId } from "./ThreadId"; + +export type ApplyPatchApprovalParams = { conversationId: ThreadId, +/** + * Use to correlate this with [codex_core::protocol::PatchApplyBeginEvent] + * and [codex_core::protocol::PatchApplyEndEvent]. + */ +callId: string, fileChanges: { [key in string]?: FileChange }, +/** + * Optional explanatory reason (e.g. request for extra write access). + */ +reason: string | null, +/** + * When set, the agent is asking the user to allow writes under this root + * for the remainder of the session (unclear if this is honored today). + */ +grantRoot: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts new file mode 100644 index 0000000000..0c53cf50b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChange } from "./FileChange"; + +export type ApplyPatchApprovalRequestEvent = { +/** + * Responses API call id for the associated patch apply call, if available. + */ +call_id: string, +/** + * Turn ID that this patch belongs to. + * Uses `#[serde(default)]` for backwards compatibility with older senders. + */ +turn_id: string, changes: { [key in string]?: FileChange }, +/** + * Optional explanatory reason (e.g. request for extra write access). + */ +reason: string | null, +/** + * When set, the agent is asking the user to allow writes under this root for the remainder of the session. + */ +grant_root: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalResponse.ts new file mode 100644 index 0000000000..e5da8d62db --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewDecision } from "./ReviewDecision"; + +export type ApplyPatchApprovalResponse = { decision: ReviewDecision, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts new file mode 100644 index 0000000000..61fbcc9fc8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type ArchiveConversationParams = { conversationId: ThreadId, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts new file mode 100644 index 0000000000..24900592b2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ArchiveConversationResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts b/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts new file mode 100644 index 0000000000..b21e86fd70 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Determines the conditions under which the user is consulted to approve + * running the command proposed by Codex. + */ +export type AskForApproval = "untrusted" | "on-failure" | "on-request" | "never"; diff --git a/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts b/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts new file mode 100644 index 0000000000..90dd930c93 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Annotations } from "./Annotations"; + +/** + * Audio provided to or from an LLM. + */ +export type AudioContent = { annotations?: Annotations, data: string, mimeType: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AuthMode.ts b/codex-rs/app-server-protocol/schema/typescript/AuthMode.ts new file mode 100644 index 0000000000..5e0cad8864 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AuthMode.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Authentication mode for OpenAI-backed providers. + */ +export type AuthMode = "apikey" | "chatgpt" | "chatgptAuthTokens"; diff --git a/codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts b/codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts new file mode 100644 index 0000000000..17cb442fe0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AuthMode } from "./AuthMode"; + +/** + * Deprecated notification. Use AccountUpdatedNotification instead. + */ +export type AuthStatusChangeNotification = { authMethod: AuthMode | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts b/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts new file mode 100644 index 0000000000..236b1dd888 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type BackgroundEventEvent = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts b/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts new file mode 100644 index 0000000000..362adcfe14 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type BlobResourceContents = { blob: string, mimeType?: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts b/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts new file mode 100644 index 0000000000..ab36a79acd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ByteRange = { +/** + * Start byte offset (inclusive) within the UTF-8 text buffer. + */ +start: number, +/** + * End byte offset (exclusive) within the UTF-8 text buffer. + */ +end: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts new file mode 100644 index 0000000000..6c6b590d1a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ContentBlock } from "./ContentBlock"; +import type { JsonValue } from "./serde_json/JsonValue"; + +/** + * The server's response to a tool call. + */ +export type CallToolResult = { content: Array, isError?: boolean, structuredContent?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts b/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts new file mode 100644 index 0000000000..dae8e8c784 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CancelLoginChatGptParams = { loginId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts b/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts new file mode 100644 index 0000000000..004e6f8ea2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CancelLoginChatGptResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientInfo.ts b/codex-rs/app-server-protocol/schema/typescript/ClientInfo.ts new file mode 100644 index 0000000000..33339b6b20 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ClientInfo.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ClientInfo = { name: string, title: string | null, version: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientNotification.ts b/codex-rs/app-server-protocol/schema/typescript/ClientNotification.ts new file mode 100644 index 0000000000..8ce2839108 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ClientNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ClientNotification = { "method": "initialized" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts new file mode 100644 index 0000000000..c323f1e03e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -0,0 +1,56 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AddConversationListenerParams } from "./AddConversationListenerParams"; +import type { ArchiveConversationParams } from "./ArchiveConversationParams"; +import type { CancelLoginChatGptParams } from "./CancelLoginChatGptParams"; +import type { ExecOneOffCommandParams } from "./ExecOneOffCommandParams"; +import type { ForkConversationParams } from "./ForkConversationParams"; +import type { FuzzyFileSearchParams } from "./FuzzyFileSearchParams"; +import type { GetAuthStatusParams } from "./GetAuthStatusParams"; +import type { GetConversationSummaryParams } from "./GetConversationSummaryParams"; +import type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams"; +import type { InitializeParams } from "./InitializeParams"; +import type { InterruptConversationParams } from "./InterruptConversationParams"; +import type { ListConversationsParams } from "./ListConversationsParams"; +import type { LoginApiKeyParams } from "./LoginApiKeyParams"; +import type { NewConversationParams } from "./NewConversationParams"; +import type { RemoveConversationListenerParams } from "./RemoveConversationListenerParams"; +import type { RequestId } from "./RequestId"; +import type { ResumeConversationParams } from "./ResumeConversationParams"; +import type { SendUserMessageParams } from "./SendUserMessageParams"; +import type { SendUserTurnParams } from "./SendUserTurnParams"; +import type { SetDefaultModelParams } from "./SetDefaultModelParams"; +import type { AppsListParams } from "./v2/AppsListParams"; +import type { CancelLoginAccountParams } from "./v2/CancelLoginAccountParams"; +import type { CollaborationModeListParams } from "./v2/CollaborationModeListParams"; +import type { CommandExecParams } from "./v2/CommandExecParams"; +import type { ConfigBatchWriteParams } from "./v2/ConfigBatchWriteParams"; +import type { ConfigReadParams } from "./v2/ConfigReadParams"; +import type { ConfigValueWriteParams } from "./v2/ConfigValueWriteParams"; +import type { FeedbackUploadParams } from "./v2/FeedbackUploadParams"; +import type { GetAccountParams } from "./v2/GetAccountParams"; +import type { ListMcpServerStatusParams } from "./v2/ListMcpServerStatusParams"; +import type { LoginAccountParams } from "./v2/LoginAccountParams"; +import type { McpServerOauthLoginParams } from "./v2/McpServerOauthLoginParams"; +import type { ModelListParams } from "./v2/ModelListParams"; +import type { ReviewStartParams } from "./v2/ReviewStartParams"; +import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams"; +import type { SkillsListParams } from "./v2/SkillsListParams"; +import type { ThreadArchiveParams } from "./v2/ThreadArchiveParams"; +import type { ThreadForkParams } from "./v2/ThreadForkParams"; +import type { ThreadListParams } from "./v2/ThreadListParams"; +import type { ThreadLoadedListParams } from "./v2/ThreadLoadedListParams"; +import type { ThreadReadParams } from "./v2/ThreadReadParams"; +import type { ThreadResumeParams } from "./v2/ThreadResumeParams"; +import type { ThreadRollbackParams } from "./v2/ThreadRollbackParams"; +import type { ThreadSetNameParams } from "./v2/ThreadSetNameParams"; +import type { ThreadStartParams } from "./v2/ThreadStartParams"; +import type { ThreadUnarchiveParams } from "./v2/ThreadUnarchiveParams"; +import type { TurnInterruptParams } from "./v2/TurnInterruptParams"; +import type { TurnStartParams } from "./v2/TurnStartParams"; + +/** + * Request from the client to the server. + */ +export type ClientRequest = { "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "collaborationMode/list", id: RequestId, params: CollaborationModeListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts new file mode 100644 index 0000000000..20dd2414bb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Codex errors that we expose to clients. + */ +export type CodexErrorInfo = "context_window_exceeded" | "usage_limit_exceeded" | { "model_cap": { model: string, reset_after_seconds: bigint | null, } } | { "http_connection_failed": { http_status_code: number | null, } } | { "response_stream_connection_failed": { http_status_code: number | null, } } | "internal_server_error" | "unauthorized" | "bad_request" | "sandbox_error" | { "response_stream_disconnected": { http_status_code: number | null, } } | { "response_too_many_failed_attempts": { http_status_code: number | null, } } | "thread_rollback_failed" | "other"; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts new file mode 100644 index 0000000000..7109741999 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentInteractionBeginEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Prompt sent from the sender to the receiver. Can be empty to prevent CoT + * leaking at the beginning. + */ +prompt: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts new file mode 100644 index 0000000000..0596300b35 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts @@ -0,0 +1,28 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentInteractionEndEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Prompt sent from the sender to the receiver. Can be empty to prevent CoT + * leaking at the beginning. + */ +prompt: string, +/** + * Last known status of the receiver agent reported to the sender agent. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts new file mode 100644 index 0000000000..a86598e20c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts @@ -0,0 +1,19 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentSpawnBeginEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the + * beginning. + */ +prompt: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts new file mode 100644 index 0000000000..e880b5a401 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts @@ -0,0 +1,28 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentSpawnEndEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the newly spawned agent, if it was created. + */ +new_thread_id: ThreadId | null, +/** + * Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the + * beginning. + */ +prompt: string, +/** + * Last known status of the new agent reported to the sender agent. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts new file mode 100644 index 0000000000..355d59523a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabCloseBeginEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts new file mode 100644 index 0000000000..70343cbe4d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts @@ -0,0 +1,24 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabCloseEndEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Last known status of the receiver agent reported to the sender agent before + * the close. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts new file mode 100644 index 0000000000..0cbe04f62b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabWaitingBeginEvent = { +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receivers. + */ +receiver_thread_ids: Array, +/** + * ID of the waiting call. + */ +call_id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts new file mode 100644 index 0000000000..57f914c134 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts @@ -0,0 +1,19 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabWaitingEndEvent = { +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * ID of the waiting call. + */ +call_id: string, +/** + * Last known status of the receiver agents reported to the sender agent. + */ +statuses: { [key in ThreadId]?: AgentStatus }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollaborationMode.ts b/codex-rs/app-server-protocol/schema/typescript/CollaborationMode.ts new file mode 100644 index 0000000000..0f60f5d104 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollaborationMode.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ModeKind } from "./ModeKind"; +import type { Settings } from "./Settings"; + +/** + * Collaboration mode for a Codex session. + */ +export type CollaborationMode = { mode: ModeKind, settings: Settings, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollaborationModeMask.ts b/codex-rs/app-server-protocol/schema/typescript/CollaborationModeMask.ts new file mode 100644 index 0000000000..05902676d7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollaborationModeMask.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ModeKind } from "./ModeKind"; +import type { ReasoningEffort } from "./ReasoningEffort"; + +/** + * A mask for collaboration mode settings, allowing partial updates. + * All fields except `name` are optional, enabling selective updates. + */ +export type CollaborationModeMask = { name: string, mode: ModeKind | null, model: string | null, reasoning_effort: ReasoningEffort | null | null, developer_instructions: string | null | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts b/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts new file mode 100644 index 0000000000..2dd62d87b4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AudioContent } from "./AudioContent"; +import type { EmbeddedResource } from "./EmbeddedResource"; +import type { ImageContent } from "./ImageContent"; +import type { ResourceLink } from "./ResourceLink"; +import type { TextContent } from "./TextContent"; + +export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContentItem.ts b/codex-rs/app-server-protocol/schema/typescript/ContentItem.ts new file mode 100644 index 0000000000..c89b9d78a4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ContentItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ContentItem = { "type": "input_text", text: string, } | { "type": "input_image", image_url: string, } | { "type": "output_text", text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts new file mode 100644 index 0000000000..538ca7a1bc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ContextCompactedEvent = null; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts b/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts new file mode 100644 index 0000000000..dc3ab6388e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ContextCompactionItem = { id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ConversationGitInfo.ts b/codex-rs/app-server-protocol/schema/typescript/ConversationGitInfo.ts new file mode 100644 index 0000000000..ff0da8383a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ConversationGitInfo.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ConversationGitInfo = { sha: string | null, branch: string | null, origin_url: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ConversationSummary.ts b/codex-rs/app-server-protocol/schema/typescript/ConversationSummary.ts new file mode 100644 index 0000000000..2cc2a05706 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ConversationSummary.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConversationGitInfo } from "./ConversationGitInfo"; +import type { SessionSource } from "./SessionSource"; +import type { ThreadId } from "./ThreadId"; + +export type ConversationSummary = { conversationId: ThreadId, path: string, preview: string, timestamp: string | null, updatedAt: string | null, modelProvider: string, cwd: string, cliVersion: string, source: SessionSource, gitInfo: ConversationGitInfo | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts new file mode 100644 index 0000000000..737bf99bef --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CreditsSnapshot = { has_credits: boolean, unlimited: boolean, balance: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts b/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts new file mode 100644 index 0000000000..96fe75e969 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CustomPrompt = { name: string, path: string, content: string, description: string | null, argument_hint: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts new file mode 100644 index 0000000000..c1a7d81314 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DeprecationNoticeEvent = { +/** + * Concise summary of what is deprecated. + */ +summary: string, +/** + * Optional extra guidance, such as migration steps or rationale. + */ +details: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts new file mode 100644 index 0000000000..94b0c65c66 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "./serde_json/JsonValue"; + +export type DynamicToolCallRequest = { callId: string, turnId: string, tool: string, arguments: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts new file mode 100644 index 0000000000..a4d641fd44 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RequestId } from "./RequestId"; + +export type ElicitationRequestEvent = { server_name: string, id: RequestId, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts new file mode 100644 index 0000000000..1b81e37ab1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Annotations } from "./Annotations"; +import type { EmbeddedResourceResource } from "./EmbeddedResourceResource"; + +/** + * The contents of a resource, embedded into a prompt or tool call result. + * + * It is up to the client how best to render embedded resources for the benefit + * of the LLM and/or the user. + */ +export type EmbeddedResource = { annotations?: Annotations, resource: EmbeddedResourceResource, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts new file mode 100644 index 0000000000..58f70f6e82 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { BlobResourceContents } from "./BlobResourceContents"; +import type { TextResourceContents } from "./TextResourceContents"; + +export type EmbeddedResourceResource = TextResourceContents | BlobResourceContents; diff --git a/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts new file mode 100644 index 0000000000..fafde767e0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CodexErrorInfo } from "./CodexErrorInfo"; + +export type ErrorEvent = { message: string, codex_error_info: CodexErrorInfo | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts b/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts new file mode 100644 index 0000000000..5f158de2cb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts @@ -0,0 +1,73 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentMessageContentDeltaEvent } from "./AgentMessageContentDeltaEvent"; +import type { AgentMessageDeltaEvent } from "./AgentMessageDeltaEvent"; +import type { AgentMessageEvent } from "./AgentMessageEvent"; +import type { AgentReasoningDeltaEvent } from "./AgentReasoningDeltaEvent"; +import type { AgentReasoningEvent } from "./AgentReasoningEvent"; +import type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent"; +import type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; +import type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; +import type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; +import type { BackgroundEventEvent } from "./BackgroundEventEvent"; +import type { CollabAgentInteractionBeginEvent } from "./CollabAgentInteractionBeginEvent"; +import type { CollabAgentInteractionEndEvent } from "./CollabAgentInteractionEndEvent"; +import type { CollabAgentSpawnBeginEvent } from "./CollabAgentSpawnBeginEvent"; +import type { CollabAgentSpawnEndEvent } from "./CollabAgentSpawnEndEvent"; +import type { CollabCloseBeginEvent } from "./CollabCloseBeginEvent"; +import type { CollabCloseEndEvent } from "./CollabCloseEndEvent"; +import type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; +import type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; +import type { ContextCompactedEvent } from "./ContextCompactedEvent"; +import type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; +import type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; +import type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; +import type { ErrorEvent } from "./ErrorEvent"; +import type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; +import type { ExecCommandBeginEvent } from "./ExecCommandBeginEvent"; +import type { ExecCommandEndEvent } from "./ExecCommandEndEvent"; +import type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent"; +import type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent"; +import type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent"; +import type { ItemCompletedEvent } from "./ItemCompletedEvent"; +import type { ItemStartedEvent } from "./ItemStartedEvent"; +import type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; +import type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; +import type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent"; +import type { McpStartupCompleteEvent } from "./McpStartupCompleteEvent"; +import type { McpStartupUpdateEvent } from "./McpStartupUpdateEvent"; +import type { McpToolCallBeginEvent } from "./McpToolCallBeginEvent"; +import type { McpToolCallEndEvent } from "./McpToolCallEndEvent"; +import type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent"; +import type { PatchApplyEndEvent } from "./PatchApplyEndEvent"; +import type { PlanDeltaEvent } from "./PlanDeltaEvent"; +import type { RawResponseItemEvent } from "./RawResponseItemEvent"; +import type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent"; +import type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; +import type { RequestUserInputEvent } from "./RequestUserInputEvent"; +import type { ReviewRequest } from "./ReviewRequest"; +import type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; +import type { StreamErrorEvent } from "./StreamErrorEvent"; +import type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; +import type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; +import type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; +import type { TokenCountEvent } from "./TokenCountEvent"; +import type { TurnAbortedEvent } from "./TurnAbortedEvent"; +import type { TurnCompleteEvent } from "./TurnCompleteEvent"; +import type { TurnDiffEvent } from "./TurnDiffEvent"; +import type { TurnStartedEvent } from "./TurnStartedEvent"; +import type { UndoCompletedEvent } from "./UndoCompletedEvent"; +import type { UndoStartedEvent } from "./UndoStartedEvent"; +import type { UpdatePlanArgs } from "./UpdatePlanArgs"; +import type { UserMessageEvent } from "./UserMessageEvent"; +import type { ViewImageToolCallEvent } from "./ViewImageToolCallEvent"; +import type { WarningEvent } from "./WarningEvent"; +import type { WebSearchBeginEvent } from "./WebSearchBeginEvent"; +import type { WebSearchEndEvent } from "./WebSearchEndEvent"; + +/** + * Response event from the agent + * NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen. + */ +export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts new file mode 100644 index 0000000000..66db8fd40a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts @@ -0,0 +1,32 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; +import type { ParsedCommand } from "./ParsedCommand"; + +export type ExecApprovalRequestEvent = { +/** + * Identifier for the associated exec call, if available. + */ +call_id: string, +/** + * Turn ID that this command belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, +/** + * The command to be executed. + */ +command: Array, +/** + * The command's working directory. + */ +cwd: string, +/** + * Optional human-readable reason for the approval (e.g. retry without sandbox). + */ +reason: string | null, +/** + * Proposed execpolicy amendment that can be applied to allow future runs. + */ +proposed_execpolicy_amendment?: ExecPolicyAmendment, parsed_cmd: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalParams.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalParams.ts new file mode 100644 index 0000000000..b427337a84 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalParams.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ParsedCommand } from "./ParsedCommand"; +import type { ThreadId } from "./ThreadId"; + +export type ExecCommandApprovalParams = { conversationId: ThreadId, +/** + * Use to correlate this with [codex_core::protocol::ExecCommandBeginEvent] + * and [codex_core::protocol::ExecCommandEndEvent]. + */ +callId: string, command: Array, cwd: string, reason: string | null, parsedCmd: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalResponse.ts new file mode 100644 index 0000000000..ce1a521614 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandApprovalResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewDecision } from "./ReviewDecision"; + +export type ExecCommandApprovalResponse = { decision: ReviewDecision, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts new file mode 100644 index 0000000000..a9b4bc9393 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts @@ -0,0 +1,35 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecCommandSource } from "./ExecCommandSource"; +import type { ParsedCommand } from "./ParsedCommand"; + +export type ExecCommandBeginEvent = { +/** + * Identifier so this can be paired with the ExecCommandEnd event. + */ +call_id: string, +/** + * Identifier for the underlying PTY process (when available). + */ +process_id?: string, +/** + * Turn ID that this command belongs to. + */ +turn_id: string, +/** + * The command to be executed. + */ +command: Array, +/** + * The command's working directory if not the default cwd for the agent. + */ +cwd: string, parsed_cmd: Array, +/** + * Where the command originated. Defaults to Agent for backward compatibility. + */ +source: ExecCommandSource, +/** + * Raw input sent to a unified exec session (if this is an interaction event). + */ +interaction_input?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts new file mode 100644 index 0000000000..c9b465e45a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts @@ -0,0 +1,59 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecCommandSource } from "./ExecCommandSource"; +import type { ParsedCommand } from "./ParsedCommand"; + +export type ExecCommandEndEvent = { +/** + * Identifier for the ExecCommandBegin that finished. + */ +call_id: string, +/** + * Identifier for the underlying PTY process (when available). + */ +process_id?: string, +/** + * Turn ID that this command belongs to. + */ +turn_id: string, +/** + * The command that was executed. + */ +command: Array, +/** + * The command's working directory if not the default cwd for the agent. + */ +cwd: string, parsed_cmd: Array, +/** + * Where the command originated. Defaults to Agent for backward compatibility. + */ +source: ExecCommandSource, +/** + * Raw input sent to a unified exec session (if this is an interaction event). + */ +interaction_input?: string, +/** + * Captured stdout + */ +stdout: string, +/** + * Captured stderr + */ +stderr: string, +/** + * Captured aggregated output + */ +aggregated_output: string, +/** + * The command's exit code. + */ +exit_code: number, +/** + * The duration of the command execution. + */ +duration: string, +/** + * Formatted output from the command, as seen by the model. + */ +formatted_output: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts new file mode 100644 index 0000000000..0930bdd827 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecOutputStream } from "./ExecOutputStream"; + +export type ExecCommandOutputDeltaEvent = { +/** + * Identifier for the ExecCommandBegin that produced this chunk. + */ +call_id: string, +/** + * Which stream produced this chunk. + */ +stream: ExecOutputStream, +/** + * Raw bytes from the stream (may not be valid UTF-8). + */ +chunk: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts new file mode 100644 index 0000000000..b665441bc2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ExecCommandSource = "agent" | "user_shell" | "unified_exec_startup" | "unified_exec_interaction"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts b/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts new file mode 100644 index 0000000000..ca28ad775c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SandboxPolicy } from "./SandboxPolicy"; + +export type ExecOneOffCommandParams = { command: Array, timeoutMs: bigint | null, cwd: string | null, sandboxPolicy: SandboxPolicy | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts new file mode 100644 index 0000000000..ff43ec4ca2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ExecOneOffCommandResponse = { exitCode: number, stdout: string, stderr: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts b/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts new file mode 100644 index 0000000000..96aa74483d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ExecOutputStream = "stdout" | "stderr"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecPolicyAmendment.ts b/codex-rs/app-server-protocol/schema/typescript/ExecPolicyAmendment.ts new file mode 100644 index 0000000000..98e2626c38 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecPolicyAmendment.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Proposed execpolicy change to allow commands starting with this prefix. + * + * The `command` tokens form the prefix that would be added as an execpolicy + * `prefix_rule(..., decision="allow")`, letting the agent bypass approval for + * commands that start with this token sequence. + */ +export type ExecPolicyAmendment = Array; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts new file mode 100644 index 0000000000..7271f07a3f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewOutputEvent } from "./ReviewOutputEvent"; + +export type ExitedReviewModeEvent = { review_output: ReviewOutputEvent | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FileChange.ts b/codex-rs/app-server-protocol/schema/typescript/FileChange.ts new file mode 100644 index 0000000000..8eaac9e8d7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FileChange.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FileChange = { "type": "add", content: string, } | { "type": "delete", content: string, } | { "type": "update", unified_diff: string, move_path: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ForcedLoginMethod.ts b/codex-rs/app-server-protocol/schema/typescript/ForcedLoginMethod.ts new file mode 100644 index 0000000000..c695908866 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ForcedLoginMethod.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ForcedLoginMethod = "chatgpt" | "api"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts new file mode 100644 index 0000000000..4ca548fbff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NewConversationParams } from "./NewConversationParams"; +import type { ThreadId } from "./ThreadId"; + +export type ForkConversationParams = { path: string | null, conversationId: ThreadId | null, overrides: NewConversationParams | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts new file mode 100644 index 0000000000..80d6e7947c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { EventMsg } from "./EventMsg"; +import type { ThreadId } from "./ThreadId"; + +export type ForkConversationResponse = { conversationId: ThreadId, model: string, initialMessages: Array | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputContentItem.ts b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputContentItem.ts new file mode 100644 index 0000000000..8bfb6993d0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputContentItem.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Responses API compatible content items that can be returned by a tool call. + * This is a subset of ContentItem with the types we support as function call outputs. + */ +export type FunctionCallOutputContentItem = { "type": "input_text", text: string, } | { "type": "input_image", image_url: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts new file mode 100644 index 0000000000..776369b10f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts @@ -0,0 +1,15 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem"; + +/** + * The payload we send back to OpenAI when reporting a tool call result. + * + * `content` preserves the historical plain-string payload so downstream + * integrations (tests, logging, etc.) can keep treating tool output as + * `String`. When an MCP server returns richer data we additionally populate + * `content_items` with the structured form that the Responses/Chat + * Completions APIs understand. + */ +export type FunctionCallOutputPayload = { content: string, content_items: Array | null, success: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchParams.ts b/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchParams.ts new file mode 100644 index 0000000000..02a7a7cfdf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FuzzyFileSearchParams = { query: string, roots: Array, cancellationToken: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResponse.ts b/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResponse.ts new file mode 100644 index 0000000000..276b94764b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FuzzyFileSearchResult } from "./FuzzyFileSearchResult"; + +export type FuzzyFileSearchResponse = { files: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResult.ts b/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResult.ts new file mode 100644 index 0000000000..e841dbfa04 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResult.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Superset of [`codex_file_search::FileMatch`] + */ +export type FuzzyFileSearchResult = { root: string, path: string, file_name: string, score: number, indices: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetAuthStatusParams.ts b/codex-rs/app-server-protocol/schema/typescript/GetAuthStatusParams.ts new file mode 100644 index 0000000000..f185a43718 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetAuthStatusParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GetAuthStatusParams = { includeToken: boolean | null, refreshToken: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetAuthStatusResponse.ts b/codex-rs/app-server-protocol/schema/typescript/GetAuthStatusResponse.ts new file mode 100644 index 0000000000..9a050f4124 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetAuthStatusResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AuthMode } from "./AuthMode"; + +export type GetAuthStatusResponse = { authMethod: AuthMode | null, authToken: string | null, requiresOpenaiAuth: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryParams.ts b/codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryParams.ts new file mode 100644 index 0000000000..4e0005430d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type GetConversationSummaryParams = { rolloutPath: string, } | { conversationId: ThreadId, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryResponse.ts b/codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryResponse.ts new file mode 100644 index 0000000000..d3dee5d621 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetConversationSummaryResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConversationSummary } from "./ConversationSummary"; + +export type GetConversationSummaryResponse = { summary: ConversationSummary, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts new file mode 100644 index 0000000000..d46019c1dc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { HistoryEntry } from "./HistoryEntry"; + +export type GetHistoryEntryResponseEvent = { offset: number, log_id: bigint, +/** + * The entry at the requested offset, if available and parseable. + */ +entry: HistoryEntry | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts b/codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts new file mode 100644 index 0000000000..a74aba5da6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GetUserAgentResponse = { userAgent: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts b/codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts new file mode 100644 index 0000000000..f8dcf2e67c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { UserSavedConfig } from "./UserSavedConfig"; + +export type GetUserSavedConfigResponse = { config: UserSavedConfig, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GhostCommit.ts b/codex-rs/app-server-protocol/schema/typescript/GhostCommit.ts new file mode 100644 index 0000000000..d7b927492b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GhostCommit.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Details of a ghost commit created from a repository state. + */ +export type GhostCommit = { id: string, parent: string | null, preexisting_untracked_files: Array, preexisting_untracked_dirs: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteParams.ts b/codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteParams.ts new file mode 100644 index 0000000000..535aad3c29 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GitDiffToRemoteParams = { cwd: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteResponse.ts b/codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteResponse.ts new file mode 100644 index 0000000000..ec6c151510 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GitDiffToRemoteResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GitSha } from "./GitSha"; + +export type GitDiffToRemoteResponse = { sha: GitSha, diff: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GitSha.ts b/codex-rs/app-server-protocol/schema/typescript/GitSha.ts new file mode 100644 index 0000000000..701b75aa0b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GitSha.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GitSha = string; diff --git a/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts b/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts new file mode 100644 index 0000000000..da5bc37c21 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type HistoryEntry = { conversation_id: string, ts: bigint, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts b/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts new file mode 100644 index 0000000000..0537639c02 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Annotations } from "./Annotations"; + +/** + * An image provided to or from an LLM. + */ +export type ImageContent = { annotations?: Annotations, data: string, mimeType: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts b/codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts new file mode 100644 index 0000000000..76c2bc10ff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ClientInfo } from "./ClientInfo"; + +export type InitializeParams = { clientInfo: ClientInfo, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts new file mode 100644 index 0000000000..8a6bec66ef --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type InitializeResponse = { userAgent: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InputItem.ts b/codex-rs/app-server-protocol/schema/typescript/InputItem.ts new file mode 100644 index 0000000000..3ac72d31d8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/InputItem.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextElement } from "./TextElement"; + +export type InputItem = { "type": "text", "data": { text: string, +/** + * UI-defined spans within `text` used to render or persist special elements. + */ +text_elements: Array, } } | { "type": "image", "data": { image_url: string, } } | { "type": "localImage", "data": { path: string, } }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts new file mode 100644 index 0000000000..8db162c97c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type InterruptConversationParams = { conversationId: ThreadId, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts new file mode 100644 index 0000000000..375604eef3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnAbortReason } from "./TurnAbortReason"; + +export type InterruptConversationResponse = { abortReason: TurnAbortReason, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts new file mode 100644 index 0000000000..97de348dff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; +import type { TurnItem } from "./TurnItem"; + +export type ItemCompletedEvent = { thread_id: ThreadId, turn_id: string, item: TurnItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts new file mode 100644 index 0000000000..e82f78f965 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; +import type { TurnItem } from "./TurnItem"; + +export type ItemStartedEvent = { thread_id: ThreadId, turn_id: string, item: TurnItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts b/codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts new file mode 100644 index 0000000000..27c9f3172a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ListConversationsParams = { pageSize: number | null, cursor: string | null, modelProviders: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts new file mode 100644 index 0000000000..0e26443a5f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConversationSummary } from "./ConversationSummary"; + +export type ListConversationsResponse = { items: Array, nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts new file mode 100644 index 0000000000..9ebb43afb7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CustomPrompt } from "./CustomPrompt"; + +/** + * Response payload for `Op::ListCustomPrompts`. + */ +export type ListCustomPromptsResponseEvent = { custom_prompts: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts new file mode 100644 index 0000000000..efdd547596 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillsListEntry } from "./SkillsListEntry"; + +/** + * Response payload for `Op::ListSkills`. + */ +export type ListSkillsResponseEvent = { skills: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LocalShellAction.ts b/codex-rs/app-server-protocol/schema/typescript/LocalShellAction.ts new file mode 100644 index 0000000000..b24847dc4e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LocalShellAction.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { LocalShellExecAction } from "./LocalShellExecAction"; + +export type LocalShellAction = { "type": "exec" } & LocalShellExecAction; diff --git a/codex-rs/app-server-protocol/schema/typescript/LocalShellExecAction.ts b/codex-rs/app-server-protocol/schema/typescript/LocalShellExecAction.ts new file mode 100644 index 0000000000..10d4133639 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LocalShellExecAction.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LocalShellExecAction = { command: Array, timeout_ms: bigint | null, working_directory: string | null, env: { [key in string]?: string } | null, user: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LocalShellStatus.ts b/codex-rs/app-server-protocol/schema/typescript/LocalShellStatus.ts new file mode 100644 index 0000000000..00db484ad6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LocalShellStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LocalShellStatus = "completed" | "in_progress" | "incomplete"; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts b/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts new file mode 100644 index 0000000000..3638553d3e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LoginApiKeyParams = { apiKey: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts b/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts new file mode 100644 index 0000000000..a67347aeb7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LoginApiKeyResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts b/codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts new file mode 100644 index 0000000000..82c07bfa2d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Deprecated in favor of AccountLoginCompletedNotification. + */ +export type LoginChatGptCompleteNotification = { loginId: string, success: boolean, error: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts b/codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts new file mode 100644 index 0000000000..4147280117 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LoginChatGptResponse = { loginId: string, authUrl: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts b/codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts new file mode 100644 index 0000000000..ad5dbd9105 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LogoutChatGptResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts b/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts new file mode 100644 index 0000000000..919ae85fd0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpAuthStatus = "unsupported" | "not_logged_in" | "bearer_token" | "o_auth"; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts b/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts new file mode 100644 index 0000000000..5b7103a60c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "./serde_json/JsonValue"; + +export type McpInvocation = { +/** + * Name of the MCP server as defined in the config. + */ +server: string, +/** + * Name of the tool as given by the MCP server. + */ +tool: string, +/** + * Arguments to the tool call. + */ +arguments: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts new file mode 100644 index 0000000000..945959431a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts @@ -0,0 +1,25 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpAuthStatus } from "./McpAuthStatus"; +import type { Resource } from "./Resource"; +import type { ResourceTemplate } from "./ResourceTemplate"; +import type { Tool } from "./Tool"; + +export type McpListToolsResponseEvent = { +/** + * Fully qualified tool name -> tool definition. + */ +tools: { [key in string]?: Tool }, +/** + * Known resources grouped by server name. + */ +resources: { [key in string]?: Array }, +/** + * Known resource templates grouped by server name. + */ +resource_templates: { [key in string]?: Array }, +/** + * Authentication status for each configured MCP server. + */ +auth_statuses: { [key in string]?: McpAuthStatus }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts new file mode 100644 index 0000000000..67354adfbe --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpStartupFailure } from "./McpStartupFailure"; + +export type McpStartupCompleteEvent = { ready: Array, failed: Array, cancelled: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts new file mode 100644 index 0000000000..b12009b15b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpStartupFailure = { server: string, error: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts new file mode 100644 index 0000000000..48c08226f4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpStartupStatus = { "state": "starting" } | { "state": "ready" } | { "state": "failed", error: string, } | { "state": "cancelled" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts new file mode 100644 index 0000000000..ecfe7d551e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpStartupStatus } from "./McpStartupStatus"; + +export type McpStartupUpdateEvent = { +/** + * Server name being started. + */ +server: string, +/** + * Current startup status. + */ +status: McpStartupStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts new file mode 100644 index 0000000000..feb7ca7c21 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpInvocation } from "./McpInvocation"; + +export type McpToolCallBeginEvent = { +/** + * Identifier so this can be paired with the McpToolCallEnd event. + */ +call_id: string, invocation: McpInvocation, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts new file mode 100644 index 0000000000..0ca82b2bc6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts @@ -0,0 +1,15 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CallToolResult } from "./CallToolResult"; +import type { McpInvocation } from "./McpInvocation"; + +export type McpToolCallEndEvent = { +/** + * Identifier for the corresponding McpToolCallBegin that finished. + */ +call_id: string, invocation: McpInvocation, duration: string, +/** + * Result of the tool call. Note this could be an error. + */ +result: { Ok : CallToolResult } | { Err : string }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ModeKind.ts b/codex-rs/app-server-protocol/schema/typescript/ModeKind.ts new file mode 100644 index 0000000000..246d440c91 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ModeKind.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Initial collaboration mode to use when the TUI starts. + */ +export type ModeKind = "plan" | "code" | "pair_programming" | "execute" | "custom"; diff --git a/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts b/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts new file mode 100644 index 0000000000..f259e67b99 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Represents whether outbound network access is available to the agent. + */ +export type NetworkAccess = "restricted" | "enabled"; diff --git a/codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts new file mode 100644 index 0000000000..e1113c27e2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AskForApproval } from "./AskForApproval"; +import type { SandboxMode } from "./SandboxMode"; +import type { JsonValue } from "./serde_json/JsonValue"; + +export type NewConversationParams = { model: string | null, modelProvider: string | null, profile: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, compactPrompt: string | null, includeApplyPatchTool: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts new file mode 100644 index 0000000000..608c2ac110 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "./ReasoningEffort"; +import type { ThreadId } from "./ThreadId"; + +export type NewConversationResponse = { conversationId: ThreadId, model: string, reasoningEffort: ReasoningEffort | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ParsedCommand.ts b/codex-rs/app-server-protocol/schema/typescript/ParsedCommand.ts new file mode 100644 index 0000000000..146d7816c2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ParsedCommand.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ParsedCommand = { "type": "read", cmd: string, name: string, +/** + * (Best effort) Path to the file being read by the command. When + * possible, this is an absolute path, though when relative, it should + * be resolved against the `cwd`` that will be used to run the command + * to derive the absolute path. + */ +path: string, } | { "type": "list_files", cmd: string, path: string | null, } | { "type": "search", cmd: string, query: string | null, path: string | null, } | { "type": "unknown", cmd: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts new file mode 100644 index 0000000000..19ff0d5754 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChange } from "./FileChange"; + +export type PatchApplyBeginEvent = { +/** + * Identifier so this can be paired with the PatchApplyEnd event. + */ +call_id: string, +/** + * Turn ID that this patch belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, +/** + * If true, there was no ApplyPatchApprovalRequest for this patch. + */ +auto_approved: boolean, +/** + * The changes to be applied. + */ +changes: { [key in string]?: FileChange }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts new file mode 100644 index 0000000000..d52940af1c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts @@ -0,0 +1,31 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChange } from "./FileChange"; + +export type PatchApplyEndEvent = { +/** + * Identifier for the PatchApplyBegin that finished. + */ +call_id: string, +/** + * Turn ID that this patch belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, +/** + * Captured stdout (summary printed by apply_patch). + */ +stdout: string, +/** + * Captured stderr (parser errors, IO failures, etc.). + */ +stderr: string, +/** + * Whether the patch was applied successfully. + */ +success: boolean, +/** + * The changes that were applied (mirrors PatchApplyBeginEvent::changes). + */ +changes: { [key in string]?: FileChange }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Personality.ts b/codex-rs/app-server-protocol/schema/typescript/Personality.ts new file mode 100644 index 0000000000..b9ccad4dc2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Personality.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Personality = "friendly" | "pragmatic"; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts new file mode 100644 index 0000000000..f2ff588442 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PlanDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts b/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts new file mode 100644 index 0000000000..909ab40e64 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PlanItem = { id: string, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts b/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts new file mode 100644 index 0000000000..a9c8acfa75 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { StepStatus } from "./StepStatus"; + +export type PlanItemArg = { step: string, status: StepStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanType.ts b/codex-rs/app-server-protocol/schema/typescript/PlanType.ts new file mode 100644 index 0000000000..9f622d0f1b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PlanType.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PlanType = "free" | "go" | "plus" | "pro" | "team" | "business" | "enterprise" | "edu" | "unknown"; diff --git a/codex-rs/app-server-protocol/schema/typescript/Profile.ts b/codex-rs/app-server-protocol/schema/typescript/Profile.ts new file mode 100644 index 0000000000..53d16e4a33 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Profile.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AskForApproval } from "./AskForApproval"; +import type { ReasoningEffort } from "./ReasoningEffort"; +import type { ReasoningSummary } from "./ReasoningSummary"; +import type { Verbosity } from "./Verbosity"; + +export type Profile = { model: string | null, modelProvider: string | null, approvalPolicy: AskForApproval | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, chatgptBaseUrl: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts new file mode 100644 index 0000000000..9c2dad7f09 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CreditsSnapshot } from "./CreditsSnapshot"; +import type { PlanType } from "./PlanType"; +import type { RateLimitWindow } from "./RateLimitWindow"; + +export type RateLimitSnapshot = { primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, plan_type: PlanType | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts b/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts new file mode 100644 index 0000000000..4a85062bf7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RateLimitWindow = { +/** + * Percentage (0-100) of the window that has been consumed. + */ +used_percent: number, +/** + * Rolling window duration, in minutes. + */ +window_minutes: number | null, +/** + * Unix timestamp (seconds since epoch) when the window resets. + */ +resets_at: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts new file mode 100644 index 0000000000..62dd4f0018 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ResponseItem } from "./ResponseItem"; + +export type RawResponseItemEvent = { item: ResponseItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts new file mode 100644 index 0000000000..70dfc01d24 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, summary_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningEffort.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningEffort.ts new file mode 100644 index 0000000000..c0798f43a3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningEffort.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning + */ +export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts new file mode 100644 index 0000000000..80bcb65fd1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningItem = { id: string, summary_text: Array, raw_content: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningItemContent.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningItemContent.ts new file mode 100644 index 0000000000..fd533796fe --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningItemContent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningItemContent = { "type": "reasoning_text", text: string, } | { "type": "text", text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningItemReasoningSummary.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningItemReasoningSummary.ts new file mode 100644 index 0000000000..f01a88a0c0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningItemReasoningSummary.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningItemReasoningSummary = { "type": "summary_text", text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts new file mode 100644 index 0000000000..ef3a792caf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningRawContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, content_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningSummary.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningSummary.ts new file mode 100644 index 0000000000..d246ac12ec --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningSummary.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * A summary of the reasoning performed by the model. This can be useful for + * debugging and understanding the model's reasoning process. + * See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries + */ +export type ReasoningSummary = "auto" | "concise" | "detailed" | "none"; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts b/codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts new file mode 100644 index 0000000000..e9628b6341 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RemoveConversationListenerParams = { subscriptionId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts b/codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts new file mode 100644 index 0000000000..8053d7e4b4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RemoveConversationSubscriptionResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestId.ts b/codex-rs/app-server-protocol/schema/typescript/RequestId.ts new file mode 100644 index 0000000000..8a771bd021 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RequestId.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RequestId = string | number; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts new file mode 100644 index 0000000000..8ea6453de9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts @@ -0,0 +1,15 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; + +export type RequestUserInputEvent = { +/** + * Responses API call id for the associated tool call, if available. + */ +call_id: string, +/** + * Turn ID that this request belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, questions: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts new file mode 100644 index 0000000000..2a68f7b4c8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; + +export type RequestUserInputQuestion = { id: string, header: string, question: string, isOther: boolean, isSecret: boolean, options: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts new file mode 100644 index 0000000000..b2d2a0db48 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RequestUserInputQuestionOption = { label: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Resource.ts b/codex-rs/app-server-protocol/schema/typescript/Resource.ts new file mode 100644 index 0000000000..238cd9008c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Resource.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Annotations } from "./Annotations"; + +/** + * A known resource that the server is capable of reading. + */ +export type Resource = { annotations?: Annotations, description?: string, mimeType?: string, name: string, size?: bigint, title?: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts b/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts new file mode 100644 index 0000000000..fe934c3a5d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Annotations } from "./Annotations"; + +/** + * A resource that the server is capable of reading, included in a prompt or tool call result. + * + * Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + */ +export type ResourceLink = { annotations?: Annotations, description?: string, mimeType?: string, name: string, size?: bigint, title?: string, type: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts b/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts new file mode 100644 index 0000000000..03c9a116c8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Annotations } from "./Annotations"; + +/** + * A template description for resources available on the server. + */ +export type ResourceTemplate = { annotations?: Annotations, description?: string, mimeType?: string, name: string, title?: string, uriTemplate: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts b/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts new file mode 100644 index 0000000000..d720021612 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ContentItem } from "./ContentItem"; +import type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload"; +import type { GhostCommit } from "./GhostCommit"; +import type { LocalShellAction } from "./LocalShellAction"; +import type { LocalShellStatus } from "./LocalShellStatus"; +import type { ReasoningItemContent } from "./ReasoningItemContent"; +import type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary"; +import type { WebSearchAction } from "./WebSearchAction"; + +export type ResponseItem = { "type": "message", role: string, content: Array, end_turn?: boolean, } | { "type": "reasoning", summary: Array, content?: Array, encrypted_content: string | null, } | { "type": "local_shell_call", +/** + * Set when using the Responses API. + */ +call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, arguments: string, call_id: string, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, output: string, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts new file mode 100644 index 0000000000..f2fe9d47c8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NewConversationParams } from "./NewConversationParams"; +import type { ResponseItem } from "./ResponseItem"; +import type { ThreadId } from "./ThreadId"; + +export type ResumeConversationParams = { path: string | null, conversationId: ThreadId | null, history: Array | null, overrides: NewConversationParams | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts new file mode 100644 index 0000000000..1af5b68599 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { EventMsg } from "./EventMsg"; +import type { ThreadId } from "./ThreadId"; + +export type ResumeConversationResponse = { conversationId: ThreadId, model: string, initialMessages: Array | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts new file mode 100644 index 0000000000..752589fe55 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewLineRange } from "./ReviewLineRange"; + +/** + * Location of the code related to a review finding. + */ +export type ReviewCodeLocation = { absolute_file_path: string, line_range: ReviewLineRange, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewDecision.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewDecision.ts new file mode 100644 index 0000000000..662fae625a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewDecision.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; + +/** + * User's decision in response to an ExecApprovalRequest. + */ +export type ReviewDecision = "approved" | { "approved_execpolicy_amendment": { proposed_execpolicy_amendment: ExecPolicyAmendment, } } | "approved_for_session" | "denied" | "abort"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts new file mode 100644 index 0000000000..e7c96bd170 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewCodeLocation } from "./ReviewCodeLocation"; + +/** + * A single review finding describing an observed issue or recommendation. + */ +export type ReviewFinding = { title: string, body: string, confidence_score: number, priority: number, code_location: ReviewCodeLocation, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts new file mode 100644 index 0000000000..c57ec6ed60 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Inclusive line range in a file associated with the finding. + */ +export type ReviewLineRange = { start: number, end: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts new file mode 100644 index 0000000000..c45747424b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewFinding } from "./ReviewFinding"; + +/** + * Structured review result produced by a child review session. + */ +export type ReviewOutputEvent = { findings: Array, overall_correctness: string, overall_explanation: string, overall_confidence_score: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts new file mode 100644 index 0000000000..1e9b8ad2ee --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewTarget } from "./ReviewTarget"; + +/** + * Review request sent to the review session. + */ +export type ReviewRequest = { target: ReviewTarget, user_facing_hint?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts new file mode 100644 index 0000000000..a79f1e993c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReviewTarget = { "type": "uncommittedChanges" } | { "type": "baseBranch", branch: string, } | { "type": "commit", sha: string, +/** + * Optional human-readable label (e.g., commit subject) for UIs. + */ +title: string | null, } | { "type": "custom", instructions: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Role.ts b/codex-rs/app-server-protocol/schema/typescript/Role.ts new file mode 100644 index 0000000000..009307efce --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Role.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * The sender or recipient of messages and data in a conversation. + */ +export type Role = "assistant" | "user"; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts new file mode 100644 index 0000000000..b8cf4326b9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access"; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts new file mode 100644 index 0000000000..103a6863f4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts @@ -0,0 +1,35 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "./AbsolutePathBuf"; +import type { NetworkAccess } from "./NetworkAccess"; + +/** + * Determines execution restrictions for model shell commands. + */ +export type SandboxPolicy = { "type": "danger-full-access" } | { "type": "read-only" } | { "type": "external-sandbox", +/** + * Whether the external sandbox permits outbound network traffic. + */ +network_access: NetworkAccess, } | { "type": "workspace-write", +/** + * Additional folders (beyond cwd and possibly TMPDIR) that should be + * writable from within the sandbox. + */ +writable_roots?: Array, +/** + * When set to `true`, outbound network access is allowed. `false` by + * default. + */ +network_access: boolean, +/** + * When set to `true`, will NOT include the per-user `TMPDIR` + * environment variable among the default writable roots. Defaults to + * `false`. + */ +exclude_tmpdir_env_var: boolean, +/** + * When set to `true`, will NOT include the `/tmp` among the default + * writable roots on UNIX. Defaults to `false`. + */ +exclude_slash_tmp: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts new file mode 100644 index 0000000000..94139b0e5d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "./AbsolutePathBuf"; + +export type SandboxSettings = { writableRoots: Array, networkAccess: boolean | null, excludeTmpdirEnvVar: boolean | null, excludeSlashTmp: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts new file mode 100644 index 0000000000..6aee538eb0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { InputItem } from "./InputItem"; +import type { ThreadId } from "./ThreadId"; + +export type SendUserMessageParams = { conversationId: ThreadId, items: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts new file mode 100644 index 0000000000..1a03e043a6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SendUserMessageResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts new file mode 100644 index 0000000000..dc4cfba8f5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts @@ -0,0 +1,16 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AskForApproval } from "./AskForApproval"; +import type { InputItem } from "./InputItem"; +import type { ReasoningEffort } from "./ReasoningEffort"; +import type { ReasoningSummary } from "./ReasoningSummary"; +import type { SandboxPolicy } from "./SandboxPolicy"; +import type { ThreadId } from "./ThreadId"; +import type { JsonValue } from "./serde_json/JsonValue"; + +export type SendUserTurnParams = { conversationId: ThreadId, items: Array, cwd: string, approvalPolicy: AskForApproval, sandboxPolicy: SandboxPolicy, model: string, effort: ReasoningEffort | null, summary: ReasoningSummary, +/** + * Optional JSON Schema used to constrain the final assistant message for this turn. + */ +outputSchema: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts new file mode 100644 index 0000000000..cffd0ac398 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SendUserTurnResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts new file mode 100644 index 0000000000..403617fcd4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts @@ -0,0 +1,39 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification"; +import type { LoginChatGptCompleteNotification } from "./LoginChatGptCompleteNotification"; +import type { SessionConfiguredNotification } from "./SessionConfiguredNotification"; +import type { AccountLoginCompletedNotification } from "./v2/AccountLoginCompletedNotification"; +import type { AccountRateLimitsUpdatedNotification } from "./v2/AccountRateLimitsUpdatedNotification"; +import type { AccountUpdatedNotification } from "./v2/AccountUpdatedNotification"; +import type { AgentMessageDeltaNotification } from "./v2/AgentMessageDeltaNotification"; +import type { CommandExecutionOutputDeltaNotification } from "./v2/CommandExecutionOutputDeltaNotification"; +import type { ConfigWarningNotification } from "./v2/ConfigWarningNotification"; +import type { ContextCompactedNotification } from "./v2/ContextCompactedNotification"; +import type { DeprecationNoticeNotification } from "./v2/DeprecationNoticeNotification"; +import type { ErrorNotification } from "./v2/ErrorNotification"; +import type { FileChangeOutputDeltaNotification } from "./v2/FileChangeOutputDeltaNotification"; +import type { ItemCompletedNotification } from "./v2/ItemCompletedNotification"; +import type { ItemStartedNotification } from "./v2/ItemStartedNotification"; +import type { McpServerOauthLoginCompletedNotification } from "./v2/McpServerOauthLoginCompletedNotification"; +import type { McpToolCallProgressNotification } from "./v2/McpToolCallProgressNotification"; +import type { PlanDeltaNotification } from "./v2/PlanDeltaNotification"; +import type { RawResponseItemCompletedNotification } from "./v2/RawResponseItemCompletedNotification"; +import type { ReasoningSummaryPartAddedNotification } from "./v2/ReasoningSummaryPartAddedNotification"; +import type { ReasoningSummaryTextDeltaNotification } from "./v2/ReasoningSummaryTextDeltaNotification"; +import type { ReasoningTextDeltaNotification } from "./v2/ReasoningTextDeltaNotification"; +import type { TerminalInteractionNotification } from "./v2/TerminalInteractionNotification"; +import type { ThreadNameUpdatedNotification } from "./v2/ThreadNameUpdatedNotification"; +import type { ThreadStartedNotification } from "./v2/ThreadStartedNotification"; +import type { ThreadTokenUsageUpdatedNotification } from "./v2/ThreadTokenUsageUpdatedNotification"; +import type { TurnCompletedNotification } from "./v2/TurnCompletedNotification"; +import type { TurnDiffUpdatedNotification } from "./v2/TurnDiffUpdatedNotification"; +import type { TurnPlanUpdatedNotification } from "./v2/TurnPlanUpdatedNotification"; +import type { TurnStartedNotification } from "./v2/TurnStartedNotification"; +import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldWritableWarningNotification"; + +/** + * Notification sent from the server to the client. + */ +export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification } | { "method": "authStatusChange", "params": AuthStatusChangeNotification } | { "method": "loginChatGptComplete", "params": LoginChatGptCompleteNotification } | { "method": "sessionConfigured", "params": SessionConfiguredNotification }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ServerRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ServerRequest.ts new file mode 100644 index 0000000000..17c66959aa --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ServerRequest.ts @@ -0,0 +1,16 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams"; +import type { ExecCommandApprovalParams } from "./ExecCommandApprovalParams"; +import type { RequestId } from "./RequestId"; +import type { ChatgptAuthTokensRefreshParams } from "./v2/ChatgptAuthTokensRefreshParams"; +import type { CommandExecutionRequestApprovalParams } from "./v2/CommandExecutionRequestApprovalParams"; +import type { DynamicToolCallParams } from "./v2/DynamicToolCallParams"; +import type { FileChangeRequestApprovalParams } from "./v2/FileChangeRequestApprovalParams"; +import type { ToolRequestUserInputParams } from "./v2/ToolRequestUserInputParams"; + +/** + * Request initiated from the server and sent to the client. + */ +export type ServerRequest = { "method": "item/commandExecution/requestApproval", id: RequestId, params: CommandExecutionRequestApprovalParams, } | { "method": "item/fileChange/requestApproval", id: RequestId, params: FileChangeRequestApprovalParams, } | { "method": "item/tool/requestUserInput", id: RequestId, params: ToolRequestUserInputParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts new file mode 100644 index 0000000000..2e1896a396 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts @@ -0,0 +1,52 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AskForApproval } from "./AskForApproval"; +import type { EventMsg } from "./EventMsg"; +import type { ReasoningEffort } from "./ReasoningEffort"; +import type { SandboxPolicy } from "./SandboxPolicy"; +import type { ThreadId } from "./ThreadId"; + +export type SessionConfiguredEvent = { session_id: ThreadId, forked_from_id: ThreadId | null, +/** + * Optional user-facing thread name (may be unset). + */ +thread_name?: string, +/** + * Tell the client what model is being queried. + */ +model: string, model_provider_id: string, +/** + * When to escalate for approval for execution + */ +approval_policy: AskForApproval, +/** + * How to sandbox commands executed in the system + */ +sandbox_policy: SandboxPolicy, +/** + * Working directory that should be treated as the *root* of the + * session. + */ +cwd: string, +/** + * The effort the model is putting into reasoning about the user's request. + */ +reasoning_effort: ReasoningEffort | null, +/** + * Identifier of the history log file (inode on Unix, 0 otherwise). + */ +history_log_id: bigint, +/** + * Current number of entries in the history log. + */ +history_entry_count: number, +/** + * Optional initial messages (as events) for resumed sessions. + * When present, UIs can use these to seed the history. + */ +initial_messages: Array | null, +/** + * Path in which the rollout is stored. Can be `None` for ephemeral threads + */ +rollout_path: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts new file mode 100644 index 0000000000..3dee74aa3a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { EventMsg } from "./EventMsg"; +import type { ReasoningEffort } from "./ReasoningEffort"; +import type { ThreadId } from "./ThreadId"; + +export type SessionConfiguredNotification = { sessionId: ThreadId, model: string, reasoningEffort: ReasoningEffort | null, historyLogId: bigint, historyEntryCount: number, initialMessages: Array | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionSource.ts b/codex-rs/app-server-protocol/schema/typescript/SessionSource.ts new file mode 100644 index 0000000000..e5e746e384 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SessionSource.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SubAgentSource } from "./SubAgentSource"; + +export type SessionSource = "cli" | "vscode" | "exec" | "mcp" | { "subagent": SubAgentSource } | "unknown"; diff --git a/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts b/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts new file mode 100644 index 0000000000..b9e4e7d901 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "./ReasoningEffort"; + +export type SetDefaultModelParams = { model: string | null, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts b/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts new file mode 100644 index 0000000000..1639601e0c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SetDefaultModelResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/Settings.ts b/codex-rs/app-server-protocol/schema/typescript/Settings.ts new file mode 100644 index 0000000000..29bcadd52e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Settings.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "./ReasoningEffort"; + +/** + * Settings for a collaboration mode. + */ +export type Settings = { model: string, reasoning_effort: ReasoningEffort | null, developer_instructions: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts b/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts new file mode 100644 index 0000000000..e2dd4f4241 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillToolDependency } from "./SkillToolDependency"; + +export type SkillDependencies = { tools: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts new file mode 100644 index 0000000000..6eaf035d8c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillErrorInfo = { path: string, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts b/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts new file mode 100644 index 0000000000..30250b9383 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillInterface = { display_name?: string, short_description?: string, icon_small?: string, icon_large?: string, brand_color?: string, default_prompt?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts new file mode 100644 index 0000000000..088abc406a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillDependencies } from "./SkillDependencies"; +import type { SkillInterface } from "./SkillInterface"; +import type { SkillScope } from "./SkillScope"; + +export type SkillMetadata = { name: string, description: string, +/** + * Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description. + */ +short_description?: string, interface?: SkillInterface, dependencies?: SkillDependencies, path: string, scope: SkillScope, enabled: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts b/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts new file mode 100644 index 0000000000..997006f5b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillScope = "user" | "repo" | "system" | "admin"; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts b/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts new file mode 100644 index 0000000000..a5da45e178 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillToolDependency = { type: string, value: string, description?: string, transport?: string, command?: string, url?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts b/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts new file mode 100644 index 0000000000..3f46c98a4a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillErrorInfo } from "./SkillErrorInfo"; +import type { SkillMetadata } from "./SkillMetadata"; + +export type SkillsListEntry = { cwd: string, skills: Array, errors: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts b/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts new file mode 100644 index 0000000000..8494a76e0b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type StepStatus = "pending" | "in_progress" | "completed"; diff --git a/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts b/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts new file mode 100644 index 0000000000..b88993a344 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CodexErrorInfo } from "./CodexErrorInfo"; + +export type StreamErrorEvent = { message: string, codex_error_info: CodexErrorInfo | null, +/** + * Optional details about the underlying stream failure (often the same + * human-readable message that is surfaced as the terminal error if retries + * are exhausted). + */ +additional_details: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SubAgentSource.ts b/codex-rs/app-server-protocol/schema/typescript/SubAgentSource.ts new file mode 100644 index 0000000000..d6da7a466b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SubAgentSource.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type SubAgentSource = "review" | "compact" | { "thread_spawn": { parent_thread_id: ThreadId, depth: number, } } | { "other": string }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts new file mode 100644 index 0000000000..5f300e6ca5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TerminalInteractionEvent = { +/** + * Identifier for the ExecCommandBegin that produced this chunk. + */ +call_id: string, +/** + * Process id associated with the running command. + */ +process_id: string, +/** + * Stdin sent to the running session. + */ +stdin: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextContent.ts b/codex-rs/app-server-protocol/schema/typescript/TextContent.ts new file mode 100644 index 0000000000..760102f81d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TextContent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Annotations } from "./Annotations"; + +/** + * Text provided to or from an LLM. + */ +export type TextContent = { annotations?: Annotations, text: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextElement.ts b/codex-rs/app-server-protocol/schema/typescript/TextElement.ts new file mode 100644 index 0000000000..8841d00499 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TextElement.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ByteRange } from "./ByteRange"; + +export type TextElement = { +/** + * Byte range in the parent `text` buffer that this element occupies. + */ +byteRange: ByteRange, +/** + * Optional human-readable placeholder for the element, displayed in the UI. + */ +placeholder: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts b/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts new file mode 100644 index 0000000000..5bef79210c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TextResourceContents = { mimeType?: string, text: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ThreadId.ts b/codex-rs/app-server-protocol/schema/typescript/ThreadId.ts new file mode 100644 index 0000000000..bfb3b4b4d7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ThreadId.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadId = string; diff --git a/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts new file mode 100644 index 0000000000..639e29f9d7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type ThreadNameUpdatedEvent = { thread_id: ThreadId, thread_name?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts new file mode 100644 index 0000000000..30bc64c9c1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadRolledBackEvent = { +/** + * Number of user turns that were removed from context. + */ +num_turns: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts new file mode 100644 index 0000000000..f58b574641 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RateLimitSnapshot } from "./RateLimitSnapshot"; +import type { TokenUsageInfo } from "./TokenUsageInfo"; + +export type TokenCountEvent = { info: TokenUsageInfo | null, rate_limits: RateLimitSnapshot | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts b/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts new file mode 100644 index 0000000000..41186b25b9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TokenUsage = { input_tokens: number, cached_input_tokens: number, output_tokens: number, reasoning_output_tokens: number, total_tokens: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts b/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts new file mode 100644 index 0000000000..cb15de42e7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TokenUsage } from "./TokenUsage"; + +export type TokenUsageInfo = { total_token_usage: TokenUsage, last_token_usage: TokenUsage, model_context_window: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Tool.ts b/codex-rs/app-server-protocol/schema/typescript/Tool.ts new file mode 100644 index 0000000000..8255eecf2c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Tool.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ToolAnnotations } from "./ToolAnnotations"; +import type { ToolInputSchema } from "./ToolInputSchema"; +import type { ToolOutputSchema } from "./ToolOutputSchema"; + +/** + * Definition for a tool the client can call. + */ +export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts b/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts new file mode 100644 index 0000000000..70348a9964 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts @@ -0,0 +1,15 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Additional properties describing a Tool to clients. + * + * NOTE: all properties in ToolAnnotations are **hints**. + * They are not guaranteed to provide a faithful description of + * tool behavior (including descriptive properties like `title`). + * + * Clients should never make tool use decisions based on ToolAnnotations + * received from untrusted servers. + */ +export type ToolAnnotations = { destructiveHint?: boolean, idempotentHint?: boolean, openWorldHint?: boolean, readOnlyHint?: boolean, title?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts b/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts new file mode 100644 index 0000000000..78f40bbe40 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "./serde_json/JsonValue"; + +/** + * A JSON Schema object defining the expected parameters for the tool. + */ +export type ToolInputSchema = { properties?: JsonValue, required?: Array, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts b/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts new file mode 100644 index 0000000000..9270c8e7b7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "./serde_json/JsonValue"; + +/** + * An optional JSON Schema object defining the structure of the tool's output returned in + * the structuredContent field of a CallToolResult. + */ +export type ToolOutputSchema = { properties?: JsonValue, required?: Array, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Tools.ts b/codex-rs/app-server-protocol/schema/typescript/Tools.ts new file mode 100644 index 0000000000..0387022966 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Tools.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Tools = { webSearch: boolean | null, viewImage: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts b/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts new file mode 100644 index 0000000000..f07cde6292 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnAbortReason = "interrupted" | "replaced" | "review_ended"; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts new file mode 100644 index 0000000000..eb0bf24c18 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnAbortReason } from "./TurnAbortReason"; + +export type TurnAbortedEvent = { reason: TurnAbortReason, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts new file mode 100644 index 0000000000..ab271ba9e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnCompleteEvent = { last_agent_message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts new file mode 100644 index 0000000000..52e3df09b0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnDiffEvent = { unified_diff: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts b/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts new file mode 100644 index 0000000000..0f2ea12a21 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentMessageItem } from "./AgentMessageItem"; +import type { ContextCompactionItem } from "./ContextCompactionItem"; +import type { PlanItem } from "./PlanItem"; +import type { ReasoningItem } from "./ReasoningItem"; +import type { UserMessageItem } from "./UserMessageItem"; +import type { WebSearchItem } from "./WebSearchItem"; + +export type TurnItem = { "type": "UserMessage" } & UserMessageItem | { "type": "AgentMessage" } & AgentMessageItem | { "type": "Plan" } & PlanItem | { "type": "Reasoning" } & ReasoningItem | { "type": "WebSearch" } & WebSearchItem | { "type": "ContextCompaction" } & ContextCompactionItem; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts new file mode 100644 index 0000000000..91598aa789 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ModeKind } from "./ModeKind"; + +export type TurnStartedEvent = { model_context_window: bigint | null, collaboration_mode_kind: ModeKind, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts new file mode 100644 index 0000000000..2d94e2e18d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UndoCompletedEvent = { success: boolean, message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts new file mode 100644 index 0000000000..712082adff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UndoStartedEvent = { message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts b/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts new file mode 100644 index 0000000000..61613fcb5f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PlanItemArg } from "./PlanItemArg"; + +export type UpdatePlanArgs = { +/** + * Arguments for the `update_plan` todo/checklist tool (not plan mode). + */ +explanation: string | null, plan: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts b/codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts new file mode 100644 index 0000000000..3d257a1c5e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UserInfoResponse = { allegedUserEmail: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserInput.ts b/codex-rs/app-server-protocol/schema/typescript/UserInput.ts new file mode 100644 index 0000000000..e6a9c3a580 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserInput.ts @@ -0,0 +1,16 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextElement } from "./TextElement"; + +/** + * User input + */ +export type UserInput = { "type": "text", text: string, +/** + * UI-defined spans within `text` that should be treated as special elements. + * These are byte ranges into the UTF-8 `text` buffer and are used to render + * or persist rich input markers (e.g., image placeholders) across history + * and resume without mutating the literal text. + */ +text_elements: Array, } | { "type": "image", image_url: string, } | { "type": "local_image", path: string, } | { "type": "skill", name: string, path: string, } | { "type": "mention", name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts new file mode 100644 index 0000000000..2fde364d67 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts @@ -0,0 +1,22 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextElement } from "./TextElement"; + +export type UserMessageEvent = { message: string, +/** + * Image URLs sourced from `UserInput::Image`. These are safe + * to replay in legacy UI history events and correspond to images sent to + * the model. + */ +images: Array | null, +/** + * Local file paths sourced from `UserInput::LocalImage`. These are kept so + * the UI can reattach images when editing history, and should not be sent + * to the model or treated as API-ready URLs. + */ +local_images: Array, +/** + * UI-defined spans within `message` used to render or persist special elements. + */ +text_elements: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts b/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts new file mode 100644 index 0000000000..df856287a5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { UserInput } from "./UserInput"; + +export type UserMessageItem = { id: string, content: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts b/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts new file mode 100644 index 0000000000..e70107f31e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AskForApproval } from "./AskForApproval"; +import type { ForcedLoginMethod } from "./ForcedLoginMethod"; +import type { Profile } from "./Profile"; +import type { ReasoningEffort } from "./ReasoningEffort"; +import type { ReasoningSummary } from "./ReasoningSummary"; +import type { SandboxMode } from "./SandboxMode"; +import type { SandboxSettings } from "./SandboxSettings"; +import type { Tools } from "./Tools"; +import type { Verbosity } from "./Verbosity"; + +export type UserSavedConfig = { approvalPolicy: AskForApproval | null, sandboxMode: SandboxMode | null, sandboxSettings: SandboxSettings | null, forcedChatgptWorkspaceId: string | null, forcedLoginMethod: ForcedLoginMethod | null, model: string | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, tools: Tools | null, profile: string | null, profiles: { [key in string]?: Profile }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Verbosity.ts b/codex-rs/app-server-protocol/schema/typescript/Verbosity.ts new file mode 100644 index 0000000000..8fd97b0b89 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/Verbosity.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Controls output length/detail on GPT-5 models via the Responses API. + * Serialized with lowercase values to match the OpenAI API. + */ +export type Verbosity = "low" | "medium" | "high"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts new file mode 100644 index 0000000000..76541a773a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ViewImageToolCallEvent = { +/** + * Identifier for the originating tool call. + */ +call_id: string, +/** + * Local filesystem path provided to the tool. + */ +path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts new file mode 100644 index 0000000000..35ec40f7cd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WarningEvent = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchAction.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchAction.ts new file mode 100644 index 0000000000..91cb99e9ed --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchAction.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WebSearchAction = { "type": "search", query?: string, queries?: Array, } | { "type": "open_page", url?: string, } | { "type": "find_in_page", url?: string, pattern?: string, } | { "type": "other" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts new file mode 100644 index 0000000000..4a8d881914 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WebSearchBeginEvent = { call_id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts new file mode 100644 index 0000000000..5b8b67c28b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { WebSearchAction } from "./WebSearchAction"; + +export type WebSearchEndEvent = { call_id: string, query: string, action: WebSearchAction, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts new file mode 100644 index 0000000000..46b1406519 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { WebSearchAction } from "./WebSearchAction"; + +export type WebSearchItem = { id: string, query: string, action: WebSearchAction, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchMode.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchMode.ts new file mode 100644 index 0000000000..695c13e3f6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchMode.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WebSearchMode = "disabled" | "cached" | "live"; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts new file mode 100644 index 0000000000..7a4a9e1d11 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -0,0 +1,229 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +export type { AbsolutePathBuf } from "./AbsolutePathBuf"; +export type { AddConversationListenerParams } from "./AddConversationListenerParams"; +export type { AddConversationSubscriptionResponse } from "./AddConversationSubscriptionResponse"; +export type { AgentMessageContent } from "./AgentMessageContent"; +export type { AgentMessageContentDeltaEvent } from "./AgentMessageContentDeltaEvent"; +export type { AgentMessageDeltaEvent } from "./AgentMessageDeltaEvent"; +export type { AgentMessageEvent } from "./AgentMessageEvent"; +export type { AgentMessageItem } from "./AgentMessageItem"; +export type { AgentReasoningDeltaEvent } from "./AgentReasoningDeltaEvent"; +export type { AgentReasoningEvent } from "./AgentReasoningEvent"; +export type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent"; +export type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; +export type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; +export type { AgentStatus } from "./AgentStatus"; +export type { Annotations } from "./Annotations"; +export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams"; +export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; +export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse"; +export type { ArchiveConversationParams } from "./ArchiveConversationParams"; +export type { ArchiveConversationResponse } from "./ArchiveConversationResponse"; +export type { AskForApproval } from "./AskForApproval"; +export type { AudioContent } from "./AudioContent"; +export type { AuthMode } from "./AuthMode"; +export type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification"; +export type { BackgroundEventEvent } from "./BackgroundEventEvent"; +export type { BlobResourceContents } from "./BlobResourceContents"; +export type { ByteRange } from "./ByteRange"; +export type { CallToolResult } from "./CallToolResult"; +export type { CancelLoginChatGptParams } from "./CancelLoginChatGptParams"; +export type { CancelLoginChatGptResponse } from "./CancelLoginChatGptResponse"; +export type { ClientInfo } from "./ClientInfo"; +export type { ClientNotification } from "./ClientNotification"; +export type { ClientRequest } from "./ClientRequest"; +export type { CodexErrorInfo } from "./CodexErrorInfo"; +export type { CollabAgentInteractionBeginEvent } from "./CollabAgentInteractionBeginEvent"; +export type { CollabAgentInteractionEndEvent } from "./CollabAgentInteractionEndEvent"; +export type { CollabAgentSpawnBeginEvent } from "./CollabAgentSpawnBeginEvent"; +export type { CollabAgentSpawnEndEvent } from "./CollabAgentSpawnEndEvent"; +export type { CollabCloseBeginEvent } from "./CollabCloseBeginEvent"; +export type { CollabCloseEndEvent } from "./CollabCloseEndEvent"; +export type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; +export type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; +export type { CollaborationMode } from "./CollaborationMode"; +export type { CollaborationModeMask } from "./CollaborationModeMask"; +export type { ContentBlock } from "./ContentBlock"; +export type { ContentItem } from "./ContentItem"; +export type { ContextCompactedEvent } from "./ContextCompactedEvent"; +export type { ContextCompactionItem } from "./ContextCompactionItem"; +export type { ConversationGitInfo } from "./ConversationGitInfo"; +export type { ConversationSummary } from "./ConversationSummary"; +export type { CreditsSnapshot } from "./CreditsSnapshot"; +export type { CustomPrompt } from "./CustomPrompt"; +export type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; +export type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; +export type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; +export type { EmbeddedResource } from "./EmbeddedResource"; +export type { EmbeddedResourceResource } from "./EmbeddedResourceResource"; +export type { ErrorEvent } from "./ErrorEvent"; +export type { EventMsg } from "./EventMsg"; +export type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; +export type { ExecCommandApprovalParams } from "./ExecCommandApprovalParams"; +export type { ExecCommandApprovalResponse } from "./ExecCommandApprovalResponse"; +export type { ExecCommandBeginEvent } from "./ExecCommandBeginEvent"; +export type { ExecCommandEndEvent } from "./ExecCommandEndEvent"; +export type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent"; +export type { ExecCommandSource } from "./ExecCommandSource"; +export type { ExecOneOffCommandParams } from "./ExecOneOffCommandParams"; +export type { ExecOneOffCommandResponse } from "./ExecOneOffCommandResponse"; +export type { ExecOutputStream } from "./ExecOutputStream"; +export type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; +export type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent"; +export type { FileChange } from "./FileChange"; +export type { ForcedLoginMethod } from "./ForcedLoginMethod"; +export type { ForkConversationParams } from "./ForkConversationParams"; +export type { ForkConversationResponse } from "./ForkConversationResponse"; +export type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem"; +export type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload"; +export type { FuzzyFileSearchParams } from "./FuzzyFileSearchParams"; +export type { FuzzyFileSearchResponse } from "./FuzzyFileSearchResponse"; +export type { FuzzyFileSearchResult } from "./FuzzyFileSearchResult"; +export type { GetAuthStatusParams } from "./GetAuthStatusParams"; +export type { GetAuthStatusResponse } from "./GetAuthStatusResponse"; +export type { GetConversationSummaryParams } from "./GetConversationSummaryParams"; +export type { GetConversationSummaryResponse } from "./GetConversationSummaryResponse"; +export type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent"; +export type { GetUserAgentResponse } from "./GetUserAgentResponse"; +export type { GetUserSavedConfigResponse } from "./GetUserSavedConfigResponse"; +export type { GhostCommit } from "./GhostCommit"; +export type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams"; +export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse"; +export type { GitSha } from "./GitSha"; +export type { HistoryEntry } from "./HistoryEntry"; +export type { ImageContent } from "./ImageContent"; +export type { InitializeParams } from "./InitializeParams"; +export type { InitializeResponse } from "./InitializeResponse"; +export type { InputItem } from "./InputItem"; +export type { InterruptConversationParams } from "./InterruptConversationParams"; +export type { InterruptConversationResponse } from "./InterruptConversationResponse"; +export type { ItemCompletedEvent } from "./ItemCompletedEvent"; +export type { ItemStartedEvent } from "./ItemStartedEvent"; +export type { ListConversationsParams } from "./ListConversationsParams"; +export type { ListConversationsResponse } from "./ListConversationsResponse"; +export type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; +export type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; +export type { LocalShellAction } from "./LocalShellAction"; +export type { LocalShellExecAction } from "./LocalShellExecAction"; +export type { LocalShellStatus } from "./LocalShellStatus"; +export type { LoginApiKeyParams } from "./LoginApiKeyParams"; +export type { LoginApiKeyResponse } from "./LoginApiKeyResponse"; +export type { LoginChatGptCompleteNotification } from "./LoginChatGptCompleteNotification"; +export type { LoginChatGptResponse } from "./LoginChatGptResponse"; +export type { LogoutChatGptResponse } from "./LogoutChatGptResponse"; +export type { McpAuthStatus } from "./McpAuthStatus"; +export type { McpInvocation } from "./McpInvocation"; +export type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent"; +export type { McpStartupCompleteEvent } from "./McpStartupCompleteEvent"; +export type { McpStartupFailure } from "./McpStartupFailure"; +export type { McpStartupStatus } from "./McpStartupStatus"; +export type { McpStartupUpdateEvent } from "./McpStartupUpdateEvent"; +export type { McpToolCallBeginEvent } from "./McpToolCallBeginEvent"; +export type { McpToolCallEndEvent } from "./McpToolCallEndEvent"; +export type { ModeKind } from "./ModeKind"; +export type { NetworkAccess } from "./NetworkAccess"; +export type { NewConversationParams } from "./NewConversationParams"; +export type { NewConversationResponse } from "./NewConversationResponse"; +export type { ParsedCommand } from "./ParsedCommand"; +export type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent"; +export type { PatchApplyEndEvent } from "./PatchApplyEndEvent"; +export type { Personality } from "./Personality"; +export type { PlanDeltaEvent } from "./PlanDeltaEvent"; +export type { PlanItem } from "./PlanItem"; +export type { PlanItemArg } from "./PlanItemArg"; +export type { PlanType } from "./PlanType"; +export type { Profile } from "./Profile"; +export type { RateLimitSnapshot } from "./RateLimitSnapshot"; +export type { RateLimitWindow } from "./RateLimitWindow"; +export type { RawResponseItemEvent } from "./RawResponseItemEvent"; +export type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent"; +export type { ReasoningEffort } from "./ReasoningEffort"; +export type { ReasoningItem } from "./ReasoningItem"; +export type { ReasoningItemContent } from "./ReasoningItemContent"; +export type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary"; +export type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; +export type { ReasoningSummary } from "./ReasoningSummary"; +export type { RemoveConversationListenerParams } from "./RemoveConversationListenerParams"; +export type { RemoveConversationSubscriptionResponse } from "./RemoveConversationSubscriptionResponse"; +export type { RequestId } from "./RequestId"; +export type { RequestUserInputEvent } from "./RequestUserInputEvent"; +export type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; +export type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; +export type { Resource } from "./Resource"; +export type { ResourceLink } from "./ResourceLink"; +export type { ResourceTemplate } from "./ResourceTemplate"; +export type { ResponseItem } from "./ResponseItem"; +export type { ResumeConversationParams } from "./ResumeConversationParams"; +export type { ResumeConversationResponse } from "./ResumeConversationResponse"; +export type { ReviewCodeLocation } from "./ReviewCodeLocation"; +export type { ReviewDecision } from "./ReviewDecision"; +export type { ReviewFinding } from "./ReviewFinding"; +export type { ReviewLineRange } from "./ReviewLineRange"; +export type { ReviewOutputEvent } from "./ReviewOutputEvent"; +export type { ReviewRequest } from "./ReviewRequest"; +export type { ReviewTarget } from "./ReviewTarget"; +export type { Role } from "./Role"; +export type { SandboxMode } from "./SandboxMode"; +export type { SandboxPolicy } from "./SandboxPolicy"; +export type { SandboxSettings } from "./SandboxSettings"; +export type { SendUserMessageParams } from "./SendUserMessageParams"; +export type { SendUserMessageResponse } from "./SendUserMessageResponse"; +export type { SendUserTurnParams } from "./SendUserTurnParams"; +export type { SendUserTurnResponse } from "./SendUserTurnResponse"; +export type { ServerNotification } from "./ServerNotification"; +export type { ServerRequest } from "./ServerRequest"; +export type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; +export type { SessionConfiguredNotification } from "./SessionConfiguredNotification"; +export type { SessionSource } from "./SessionSource"; +export type { SetDefaultModelParams } from "./SetDefaultModelParams"; +export type { SetDefaultModelResponse } from "./SetDefaultModelResponse"; +export type { Settings } from "./Settings"; +export type { SkillDependencies } from "./SkillDependencies"; +export type { SkillErrorInfo } from "./SkillErrorInfo"; +export type { SkillInterface } from "./SkillInterface"; +export type { SkillMetadata } from "./SkillMetadata"; +export type { SkillScope } from "./SkillScope"; +export type { SkillToolDependency } from "./SkillToolDependency"; +export type { SkillsListEntry } from "./SkillsListEntry"; +export type { StepStatus } from "./StepStatus"; +export type { StreamErrorEvent } from "./StreamErrorEvent"; +export type { SubAgentSource } from "./SubAgentSource"; +export type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; +export type { TextContent } from "./TextContent"; +export type { TextElement } from "./TextElement"; +export type { TextResourceContents } from "./TextResourceContents"; +export type { ThreadId } from "./ThreadId"; +export type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; +export type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; +export type { TokenCountEvent } from "./TokenCountEvent"; +export type { TokenUsage } from "./TokenUsage"; +export type { TokenUsageInfo } from "./TokenUsageInfo"; +export type { Tool } from "./Tool"; +export type { ToolAnnotations } from "./ToolAnnotations"; +export type { ToolInputSchema } from "./ToolInputSchema"; +export type { ToolOutputSchema } from "./ToolOutputSchema"; +export type { Tools } from "./Tools"; +export type { TurnAbortReason } from "./TurnAbortReason"; +export type { TurnAbortedEvent } from "./TurnAbortedEvent"; +export type { TurnCompleteEvent } from "./TurnCompleteEvent"; +export type { TurnDiffEvent } from "./TurnDiffEvent"; +export type { TurnItem } from "./TurnItem"; +export type { TurnStartedEvent } from "./TurnStartedEvent"; +export type { UndoCompletedEvent } from "./UndoCompletedEvent"; +export type { UndoStartedEvent } from "./UndoStartedEvent"; +export type { UpdatePlanArgs } from "./UpdatePlanArgs"; +export type { UserInfoResponse } from "./UserInfoResponse"; +export type { UserInput } from "./UserInput"; +export type { UserMessageEvent } from "./UserMessageEvent"; +export type { UserMessageItem } from "./UserMessageItem"; +export type { UserSavedConfig } from "./UserSavedConfig"; +export type { Verbosity } from "./Verbosity"; +export type { ViewImageToolCallEvent } from "./ViewImageToolCallEvent"; +export type { WarningEvent } from "./WarningEvent"; +export type { WebSearchAction } from "./WebSearchAction"; +export type { WebSearchBeginEvent } from "./WebSearchBeginEvent"; +export type { WebSearchEndEvent } from "./WebSearchEndEvent"; +export type { WebSearchItem } from "./WebSearchItem"; +export type { WebSearchMode } from "./WebSearchMode"; +export * as v2 from "./v2"; diff --git a/codex-rs/app-server-protocol/schema/typescript/serde_json/JsonValue.ts b/codex-rs/app-server-protocol/schema/typescript/serde_json/JsonValue.ts new file mode 100644 index 0000000000..75cf7389ad --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/serde_json/JsonValue.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type JsonValue = number | string | boolean | Array | { [key in string]?: JsonValue } | null; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts new file mode 100644 index 0000000000..f91677499e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PlanType } from "../PlanType"; + +export type Account = { "type": "apiKey", } | { "type": "chatgpt", email: string, planType: PlanType, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AccountLoginCompletedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AccountLoginCompletedNotification.ts new file mode 100644 index 0000000000..587237b275 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AccountLoginCompletedNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AccountLoginCompletedNotification = { loginId: string | null, success: boolean, error: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AccountRateLimitsUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AccountRateLimitsUpdatedNotification.ts new file mode 100644 index 0000000000..96c735a2eb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AccountRateLimitsUpdatedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RateLimitSnapshot } from "./RateLimitSnapshot"; + +export type AccountRateLimitsUpdatedNotification = { rateLimits: RateLimitSnapshot, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts new file mode 100644 index 0000000000..eacb815412 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AuthMode } from "../AuthMode"; + +export type AccountUpdatedNotification = { authMode: AuthMode | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AgentMessageDeltaNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AgentMessageDeltaNotification.ts new file mode 100644 index 0000000000..b47985e5b7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AgentMessageDeltaNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageDeltaNotification = { threadId: string, turnId: string, itemId: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AnalyticsConfig.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AnalyticsConfig.ts new file mode 100644 index 0000000000..d095439aee --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AnalyticsConfig.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; + +export type AnalyticsConfig = { enabled: boolean | null, } & ({ [key in string]?: number | string | boolean | Array | { [key in string]?: JsonValue } | null }); diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts new file mode 100644 index 0000000000..6e959cc2ef --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AppInfo = { id: string, name: string, description: string | null, logoUrl: string | null, logoUrlDark: string | null, distributionChannel: string | null, installUrl: string | null, isAccessible: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts new file mode 100644 index 0000000000..fe925998bf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AppsListParams = { +/** + * Opaque pagination cursor returned by a previous call. + */ +cursor: string | null, +/** + * Optional page size; defaults to a reasonable server-side value. + */ +limit: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AppsListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AppsListResponse.ts new file mode 100644 index 0000000000..b6f5c653f2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AppsListResponse.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AppInfo } from "./AppInfo"; + +export type AppsListResponse = { data: Array, +/** + * Opaque cursor to pass to the next call to continue after the last item. + * If None, there are no more items to return. + */ +nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AskForApproval.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AskForApproval.ts new file mode 100644 index 0000000000..d3c3e77e39 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AskForApproval.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AskForApproval = "untrusted" | "on-failure" | "on-request" | "never"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ByteRange.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ByteRange.ts new file mode 100644 index 0000000000..6cb81b87c0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ByteRange.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ByteRange = { start: number, end: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountParams.ts new file mode 100644 index 0000000000..8e2e90dfb6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CancelLoginAccountParams = { loginId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountResponse.ts new file mode 100644 index 0000000000..2e7b3d03fe --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CancelLoginAccountStatus } from "./CancelLoginAccountStatus"; + +export type CancelLoginAccountResponse = { status: CancelLoginAccountStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountStatus.ts new file mode 100644 index 0000000000..bd851c6a39 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CancelLoginAccountStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CancelLoginAccountStatus = "canceled" | "notFound"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts new file mode 100644 index 0000000000..75e677f5c6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts @@ -0,0 +1,16 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ChatgptAuthTokensRefreshReason } from "./ChatgptAuthTokensRefreshReason"; + +export type ChatgptAuthTokensRefreshParams = { reason: ChatgptAuthTokensRefreshReason, +/** + * Workspace/account identifier that Codex was previously using. + * + * Clients that manage multiple accounts/workspaces can use this as a hint + * to refresh the token for the correct workspace. + * + * This may be `null` when the prior ID token did not include a workspace + * identifier (`chatgpt_account_id`) or when the token could not be parsed. + */ +previousAccountId: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshReason.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshReason.ts new file mode 100644 index 0000000000..ac4006ba6a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshReason.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ChatgptAuthTokensRefreshReason = "unauthorized"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshResponse.ts new file mode 100644 index 0000000000..f7f7ecba89 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ChatgptAuthTokensRefreshResponse = { idToken: string, accessToken: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CodexErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CodexErrorInfo.ts new file mode 100644 index 0000000000..a1e65c30c0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CodexErrorInfo.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * This translation layer make sure that we expose codex error code in camel case. + * + * When an upstream HTTP status is available (for example, from the Responses API or a provider), + * it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant. + */ +export type CodexErrorInfo = "contextWindowExceeded" | "usageLimitExceeded" | { "modelCap": { model: string, reset_after_seconds: bigint | null, } } | { "httpConnectionFailed": { httpStatusCode: number | null, } } | { "responseStreamConnectionFailed": { httpStatusCode: number | null, } } | "internalServerError" | "unauthorized" | "badRequest" | "threadRollbackFailed" | "sandboxError" | { "responseStreamDisconnected": { httpStatusCode: number | null, } } | { "responseTooManyFailedAttempts": { httpStatusCode: number | null, } } | "other"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentState.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentState.ts new file mode 100644 index 0000000000..785dbf1fe0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentState.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CollabAgentStatus } from "./CollabAgentStatus"; + +export type CollabAgentState = { status: CollabAgentStatus, message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts new file mode 100644 index 0000000000..3672d19dac --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CollabAgentStatus = "pendingInit" | "running" | "completed" | "errored" | "shutdown" | "notFound"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentTool.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentTool.ts new file mode 100644 index 0000000000..11db4dbf9a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentTool.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CollabAgentTool = "spawnAgent" | "sendInput" | "wait" | "closeAgent"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentToolCallStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentToolCallStatus.ts new file mode 100644 index 0000000000..f21f7bd5d5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentToolCallStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CollabAgentToolCallStatus = "inProgress" | "completed" | "failed"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts new file mode 100644 index 0000000000..37e8f792d9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * EXPERIMENTAL - list collaboration mode presets. + */ +export type CollaborationModeListParams = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts new file mode 100644 index 0000000000..6b741a36ac --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CollaborationModeMask } from "../CollaborationModeMask"; + +/** + * EXPERIMENTAL - collaboration mode presets response. + */ +export type CollaborationModeListResponse = { data: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandAction.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandAction.ts new file mode 100644 index 0000000000..ac1314c89b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandAction.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CommandAction = { "type": "read", command: string, name: string, path: string, } | { "type": "listFiles", command: string, path: string | null, } | { "type": "search", command: string, query: string | null, path: string | null, } | { "type": "unknown", command: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts new file mode 100644 index 0000000000..fc9c6a39ce --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SandboxPolicy } from "./SandboxPolicy"; + +export type CommandExecParams = { command: Array, timeoutMs: number | null, cwd: string | null, sandboxPolicy: SandboxPolicy | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecResponse.ts new file mode 100644 index 0000000000..6887a3e3c2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CommandExecResponse = { exitCode: number, stdout: string, stderr: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionApprovalDecision.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionApprovalDecision.ts new file mode 100644 index 0000000000..80df9bd02c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionApprovalDecision.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; + +export type CommandExecutionApprovalDecision = "accept" | "acceptForSession" | { "acceptWithExecpolicyAmendment": { execpolicy_amendment: ExecPolicyAmendment, } } | "decline" | "cancel"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionOutputDeltaNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionOutputDeltaNotification.ts new file mode 100644 index 0000000000..90a4ae17e6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionOutputDeltaNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CommandExecutionOutputDeltaNotification = { threadId: string, turnId: string, itemId: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts new file mode 100644 index 0000000000..3bad1b467c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts @@ -0,0 +1,27 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CommandAction } from "./CommandAction"; +import type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; + +export type CommandExecutionRequestApprovalParams = { threadId: string, turnId: string, itemId: string, +/** + * Optional explanatory reason (e.g. request for network access). + */ +reason: string | null, +/** + * The command to be executed. + */ +command?: string, +/** + * The command's working directory. + */ +cwd?: string, +/** + * Best-effort parsed command actions for friendly display. + */ +commandActions?: Array, +/** + * Optional proposed execpolicy amendment to allow similar commands without prompting. + */ +proposedExecpolicyAmendment: ExecPolicyAmendment | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalResponse.ts new file mode 100644 index 0000000000..33df225621 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CommandExecutionApprovalDecision } from "./CommandExecutionApprovalDecision"; + +export type CommandExecutionRequestApprovalResponse = { decision: CommandExecutionApprovalDecision, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionStatus.ts new file mode 100644 index 0000000000..c58b3cc7fa --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CommandExecutionStatus = "inProgress" | "completed" | "failed" | "declined"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Config.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Config.ts new file mode 100644 index 0000000000..22e84c8f36 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Config.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ForcedLoginMethod } from "../ForcedLoginMethod"; +import type { ReasoningEffort } from "../ReasoningEffort"; +import type { ReasoningSummary } from "../ReasoningSummary"; +import type { Verbosity } from "../Verbosity"; +import type { WebSearchMode } from "../WebSearchMode"; +import type { JsonValue } from "../serde_json/JsonValue"; +import type { AnalyticsConfig } from "./AnalyticsConfig"; +import type { AskForApproval } from "./AskForApproval"; +import type { ProfileV2 } from "./ProfileV2"; +import type { SandboxMode } from "./SandboxMode"; +import type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite"; +import type { ToolsV2 } from "./ToolsV2"; + +export type Config = { model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, analytics: AnalyticsConfig | null, } & ({ [key in string]?: number | string | boolean | Array | { [key in string]?: JsonValue } | null }); diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts new file mode 100644 index 0000000000..c4012fc339 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConfigEdit } from "./ConfigEdit"; + +export type ConfigBatchWriteParams = { edits: Array, +/** + * Path to the config file to write; defaults to the user's `config.toml` when omitted. + */ +filePath: string | null, expectedVersion: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigEdit.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigEdit.ts new file mode 100644 index 0000000000..fee14aab86 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigEdit.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; +import type { MergeStrategy } from "./MergeStrategy"; + +export type ConfigEdit = { keyPath: string, value: JsonValue, mergeStrategy: MergeStrategy, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayer.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayer.ts new file mode 100644 index 0000000000..6fe7c99130 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayer.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; +import type { ConfigLayerSource } from "./ConfigLayerSource"; + +export type ConfigLayer = { name: ConfigLayerSource, version: string, config: JsonValue, disabledReason: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerMetadata.ts new file mode 100644 index 0000000000..fbb334e5fa --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerMetadata.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConfigLayerSource } from "./ConfigLayerSource"; + +export type ConfigLayerMetadata = { name: ConfigLayerSource, version: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts new file mode 100644 index 0000000000..b20c373bcb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts @@ -0,0 +1,16 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "../AbsolutePathBuf"; + +export type ConfigLayerSource = { "type": "mdm", domain: string, key: string, } | { "type": "system", +/** + * This is the path to the system config.toml file, though it is not + * guaranteed to exist. + */ +file: AbsolutePathBuf, } | { "type": "user", +/** + * This is the path to the user's config.toml file, though it is not + * guaranteed to exist. + */ +file: AbsolutePathBuf, } | { "type": "project", dotCodexFolder: AbsolutePathBuf, } | { "type": "sessionFlags" } | { "type": "legacyManagedConfigTomlFromFile", file: AbsolutePathBuf, } | { "type": "legacyManagedConfigTomlFromMdm" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts new file mode 100644 index 0000000000..3cb2394bc5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ConfigReadParams = { includeLayers: boolean, +/** + * Optional working directory to resolve project config layers. If specified, + * return the effective config as seen from that directory (i.e., including any + * project layers between `cwd` and the project/repo root). + */ +cwd: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadResponse.ts new file mode 100644 index 0000000000..6b9c6a5c9a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadResponse.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Config } from "./Config"; +import type { ConfigLayer } from "./ConfigLayer"; +import type { ConfigLayerMetadata } from "./ConfigLayerMetadata"; + +export type ConfigReadResponse = { config: Config, origins: { [key in string]?: ConfigLayerMetadata }, layers: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts new file mode 100644 index 0000000000..765d0b86cf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AskForApproval } from "./AskForApproval"; +import type { ResidencyRequirement } from "./ResidencyRequirement"; +import type { SandboxMode } from "./SandboxMode"; + +export type ConfigRequirements = { allowedApprovalPolicies: Array | null, allowedSandboxModes: Array | null, enforceResidency: ResidencyRequirement | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirementsReadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirementsReadResponse.ts new file mode 100644 index 0000000000..c2891d939e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirementsReadResponse.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConfigRequirements } from "./ConfigRequirements"; + +export type ConfigRequirementsReadResponse = { +/** + * Null if no requirements are configured (e.g. no requirements.toml/MDM entries). + */ +requirements: ConfigRequirements | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts new file mode 100644 index 0000000000..2dedfe8d02 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; +import type { MergeStrategy } from "./MergeStrategy"; + +export type ConfigValueWriteParams = { keyPath: string, value: JsonValue, mergeStrategy: MergeStrategy, +/** + * Path to the config file to write; defaults to the user's `config.toml` when omitted. + */ +filePath: string | null, expectedVersion: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigWarningNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigWarningNotification.ts new file mode 100644 index 0000000000..fae64c7a2c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigWarningNotification.ts @@ -0,0 +1,22 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextRange } from "./TextRange"; + +export type ConfigWarningNotification = { +/** + * Concise summary of the warning. + */ +summary: string, +/** + * Optional extra guidance or error details. + */ +details: string | null, +/** + * Optional path to the config file that triggered the warning. + */ +path?: string, +/** + * Optional range for the error location inside the config file. + */ +range?: TextRange, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigWriteResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigWriteResponse.ts new file mode 100644 index 0000000000..536a680b20 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigWriteResponse.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "../AbsolutePathBuf"; +import type { OverriddenMetadata } from "./OverriddenMetadata"; +import type { WriteStatus } from "./WriteStatus"; + +export type ConfigWriteResponse = { status: WriteStatus, version: string, +/** + * Canonical path to the config file that was written. + */ +filePath: AbsolutePathBuf, overriddenMetadata: OverriddenMetadata | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ContextCompactedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ContextCompactedNotification.ts new file mode 100644 index 0000000000..6927609de7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ContextCompactedNotification.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Deprecated: Use `ContextCompaction` item type instead. + */ +export type ContextCompactedNotification = { threadId: string, turnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CreditsSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CreditsSnapshot.ts new file mode 100644 index 0000000000..94577df690 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CreditsSnapshot.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CreditsSnapshot = { hasCredits: boolean, unlimited: boolean, balance: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeprecationNoticeNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeprecationNoticeNotification.ts new file mode 100644 index 0000000000..e0d2e7d6e6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeprecationNoticeNotification.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DeprecationNoticeNotification = { +/** + * Concise summary of what is deprecated. + */ +summary: string, +/** + * Optional extra guidance, such as migration steps or rationale. + */ +details: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallParams.ts new file mode 100644 index 0000000000..2659da3505 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; + +export type DynamicToolCallParams = { threadId: string, turnId: string, callId: string, tool: string, arguments: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts new file mode 100644 index 0000000000..a35b9b394a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DynamicToolCallResponse = { output: string, success: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSpec.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSpec.ts new file mode 100644 index 0000000000..8b39793f3f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSpec.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; + +export type DynamicToolSpec = { name: string, description: string, inputSchema: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ErrorNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ErrorNotification.ts new file mode 100644 index 0000000000..c3032883d4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ErrorNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnError } from "./TurnError"; + +export type ErrorNotification = { error: TurnError, willRetry: boolean, threadId: string, turnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExecPolicyAmendment.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExecPolicyAmendment.ts new file mode 100644 index 0000000000..e893dd4477 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExecPolicyAmendment.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ExecPolicyAmendment = Array; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts new file mode 100644 index 0000000000..aeccc45773 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FeedbackUploadParams = { classification: string, reason: string | null, threadId: string | null, includeLogs: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadResponse.ts new file mode 100644 index 0000000000..f0ad9784c0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FeedbackUploadResponse = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeApprovalDecision.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeApprovalDecision.ts new file mode 100644 index 0000000000..b74ba004b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeApprovalDecision.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FileChangeApprovalDecision = "accept" | "acceptForSession" | "decline" | "cancel"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeOutputDeltaNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeOutputDeltaNotification.ts new file mode 100644 index 0000000000..1018bd8a2b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeOutputDeltaNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FileChangeOutputDeltaNotification = { threadId: string, turnId: string, itemId: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts new file mode 100644 index 0000000000..a7dbfbb7c0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FileChangeRequestApprovalParams = { threadId: string, turnId: string, itemId: string, +/** + * Optional explanatory reason (e.g. request for extra write access). + */ +reason: string | null, +/** + * [UNSTABLE] When set, the agent is asking the user to allow writes under this root + * for the remainder of the session (unclear if this is honored today). + */ +grantRoot: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalResponse.ts new file mode 100644 index 0000000000..6f5de6e958 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChangeApprovalDecision } from "./FileChangeApprovalDecision"; + +export type FileChangeRequestApprovalResponse = { decision: FileChangeApprovalDecision, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FileUpdateChange.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FileUpdateChange.ts new file mode 100644 index 0000000000..c724db2b10 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FileUpdateChange.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PatchChangeKind } from "./PatchChangeKind"; + +export type FileUpdateChange = { path: string, kind: PatchChangeKind, diff: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountParams.ts new file mode 100644 index 0000000000..efc646d16d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GetAccountParams = { +/** + * When `true`, requests a proactive token refresh before returning. + * + * In managed auth mode this triggers the normal refresh-token flow. In + * external auth mode this flag is ignored. Clients should refresh tokens + * themselves and call `account/login/start` with `chatgptAuthTokens`. + */ +refreshToken: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountRateLimitsResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountRateLimitsResponse.ts new file mode 100644 index 0000000000..fe970c1d42 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountRateLimitsResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RateLimitSnapshot } from "./RateLimitSnapshot"; + +export type GetAccountRateLimitsResponse = { rateLimits: RateLimitSnapshot, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts new file mode 100644 index 0000000000..83da4f4e5e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Account } from "./Account"; + +export type GetAccountResponse = { account: Account | null, requiresOpenaiAuth: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/GitInfo.ts b/codex-rs/app-server-protocol/schema/typescript/v2/GitInfo.ts new file mode 100644 index 0000000000..9559272a0f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/GitInfo.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GitInfo = { sha: string | null, branch: string | null, originUrl: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ItemCompletedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ItemCompletedNotification.ts new file mode 100644 index 0000000000..96122204b4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ItemCompletedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadItem } from "./ThreadItem"; + +export type ItemCompletedNotification = { item: ThreadItem, threadId: string, turnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ItemStartedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ItemStartedNotification.ts new file mode 100644 index 0000000000..5cf1e7b918 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ItemStartedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadItem } from "./ThreadItem"; + +export type ItemStartedNotification = { item: ThreadItem, threadId: string, turnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts new file mode 100644 index 0000000000..8027087fd8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ListMcpServerStatusParams = { +/** + * Opaque pagination cursor returned by a previous call. + */ +cursor: string | null, +/** + * Optional page size; defaults to a server-defined value. + */ +limit: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusResponse.ts new file mode 100644 index 0000000000..35a92bdcb9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusResponse.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpServerStatus } from "./McpServerStatus"; + +export type ListMcpServerStatusResponse = { data: Array, +/** + * Opaque cursor to pass to the next call to continue after the last item. + * If None, there are no more items to return. + */ +nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts new file mode 100644 index 0000000000..5c1f4c02a5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt" } | { "type": "chatgptAuthTokens", +/** + * ID token (JWT) supplied by the client. + * + * This token is used for identity and account metadata (email, plan type, + * workspace id). + */ +idToken: string, +/** + * Access token (JWT) supplied by the client. + * This token is used for backend API requests. + */ +accessToken: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts new file mode 100644 index 0000000000..cd79f6c83f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LoginAccountResponse = { "type": "apiKey", } | { "type": "chatgpt", loginId: string, +/** + * URL the client should open in a browser to initiate the OAuth flow. + */ +authUrl: string, } | { "type": "chatgptAuthTokens", }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/LogoutAccountResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/LogoutAccountResponse.ts new file mode 100644 index 0000000000..ec85cf0ff7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/LogoutAccountResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LogoutAccountResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpAuthStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpAuthStatus.ts new file mode 100644 index 0000000000..6903a12321 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpAuthStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpAuthStatus = "unsupported" | "notLoggedIn" | "bearerToken" | "oAuth"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginCompletedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginCompletedNotification.ts new file mode 100644 index 0000000000..592860ae39 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginCompletedNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpServerOauthLoginCompletedNotification = { name: string, success: boolean, error?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts new file mode 100644 index 0000000000..0ad8f3948a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpServerOauthLoginParams = { name: string, scopes?: Array, timeoutSecs?: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginResponse.ts new file mode 100644 index 0000000000..5933574765 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpServerOauthLoginResponse = { authorizationUrl: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpServerRefreshResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerRefreshResponse.ts new file mode 100644 index 0000000000..48a25d2fec --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerRefreshResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpServerRefreshResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpServerStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerStatus.ts new file mode 100644 index 0000000000..430494e268 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerStatus.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Resource } from "../Resource"; +import type { ResourceTemplate } from "../ResourceTemplate"; +import type { Tool } from "../Tool"; +import type { McpAuthStatus } from "./McpAuthStatus"; + +export type McpServerStatus = { name: string, tools: { [key in string]?: Tool }, resources: Array, resourceTemplates: Array, authStatus: McpAuthStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallError.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallError.ts new file mode 100644 index 0000000000..5e4ae8391b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallError.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpToolCallError = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallProgressNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallProgressNotification.ts new file mode 100644 index 0000000000..c255de2709 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallProgressNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpToolCallProgressNotification = { threadId: string, turnId: string, itemId: string, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts new file mode 100644 index 0000000000..93f942a7f9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ContentBlock } from "../ContentBlock"; +import type { JsonValue } from "../serde_json/JsonValue"; + +export type McpToolCallResult = { content: Array, structuredContent: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallStatus.ts new file mode 100644 index 0000000000..f46bca07e8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpToolCallStatus = "inProgress" | "completed" | "failed"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/MergeStrategy.ts b/codex-rs/app-server-protocol/schema/typescript/v2/MergeStrategy.ts new file mode 100644 index 0000000000..098677f289 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/MergeStrategy.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type MergeStrategy = "replace" | "upsert"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts new file mode 100644 index 0000000000..b664024d0a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "../ReasoningEffort"; +import type { ReasoningEffortOption } from "./ReasoningEffortOption"; + +export type Model = { id: string, model: string, displayName: string, description: string, supportedReasoningEfforts: Array, defaultReasoningEffort: ReasoningEffort, supportsPersonality: boolean, isDefault: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts new file mode 100644 index 0000000000..a2b7214b7b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ModelListParams = { +/** + * Opaque pagination cursor returned by a previous call. + */ +cursor: string | null, +/** + * Optional page size; defaults to a reasonable server-side value. + */ +limit: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ModelListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ModelListResponse.ts new file mode 100644 index 0000000000..be5ba25dc8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ModelListResponse.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Model } from "./Model"; + +export type ModelListResponse = { data: Array, +/** + * Opaque cursor to pass to the next call to continue after the last item. + * If None, there are no more items to return. + */ +nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/NetworkAccess.ts b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkAccess.ts new file mode 100644 index 0000000000..7b697b2314 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkAccess.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NetworkAccess = "restricted" | "enabled"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/OverriddenMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/v2/OverriddenMetadata.ts new file mode 100644 index 0000000000..0f6396bb54 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/OverriddenMetadata.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; +import type { ConfigLayerMetadata } from "./ConfigLayerMetadata"; + +export type OverriddenMetadata = { message: string, overridingLayer: ConfigLayerMetadata, effectiveValue: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PatchApplyStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PatchApplyStatus.ts new file mode 100644 index 0000000000..620be789e4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PatchApplyStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PatchApplyStatus = "inProgress" | "completed" | "failed" | "declined"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PatchChangeKind.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PatchChangeKind.ts new file mode 100644 index 0000000000..23dda6cb12 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PatchChangeKind.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PatchChangeKind = { "type": "add" } | { "type": "delete" } | { "type": "update", move_path: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PlanDeltaNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PlanDeltaNotification.ts new file mode 100644 index 0000000000..5ab359668e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PlanDeltaNotification.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should + * not assume concatenated deltas match the completed plan item content. + */ +export type PlanDeltaNotification = { threadId: string, turnId: string, itemId: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts new file mode 100644 index 0000000000..56428ba7ab --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "../ReasoningEffort"; +import type { ReasoningSummary } from "../ReasoningSummary"; +import type { Verbosity } from "../Verbosity"; +import type { WebSearchMode } from "../WebSearchMode"; +import type { JsonValue } from "../serde_json/JsonValue"; +import type { AskForApproval } from "./AskForApproval"; + +export type ProfileV2 = { model: string | null, model_provider: string | null, approval_policy: AskForApproval | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, chatgpt_base_url: string | null, } & ({ [key in string]?: number | string | boolean | Array | { [key in string]?: JsonValue } | null }); diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts new file mode 100644 index 0000000000..f1a33f0b13 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PlanType } from "../PlanType"; +import type { CreditsSnapshot } from "./CreditsSnapshot"; +import type { RateLimitWindow } from "./RateLimitWindow"; + +export type RateLimitSnapshot = { primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, planType: PlanType | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitWindow.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitWindow.ts new file mode 100644 index 0000000000..5031f8d93b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitWindow.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RateLimitWindow = { usedPercent: number, windowDurationMins: number | null, resetsAt: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RawResponseItemCompletedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RawResponseItemCompletedNotification.ts new file mode 100644 index 0000000000..430c3a066e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RawResponseItemCompletedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ResponseItem } from "../ResponseItem"; + +export type RawResponseItemCompletedNotification = { threadId: string, turnId: string, item: ResponseItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningEffortOption.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningEffortOption.ts new file mode 100644 index 0000000000..ec18adfe43 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningEffortOption.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "../ReasoningEffort"; + +export type ReasoningEffortOption = { reasoningEffort: ReasoningEffort, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryPartAddedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryPartAddedNotification.ts new file mode 100644 index 0000000000..3585812505 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryPartAddedNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningSummaryPartAddedNotification = { threadId: string, turnId: string, itemId: string, summaryIndex: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryTextDeltaNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryTextDeltaNotification.ts new file mode 100644 index 0000000000..aa932fa524 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningSummaryTextDeltaNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningSummaryTextDeltaNotification = { threadId: string, turnId: string, itemId: string, delta: string, summaryIndex: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningTextDeltaNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningTextDeltaNotification.ts new file mode 100644 index 0000000000..86584ba3b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReasoningTextDeltaNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningTextDeltaNotification = { threadId: string, turnId: string, itemId: string, delta: string, contentIndex: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ResidencyRequirement.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ResidencyRequirement.ts new file mode 100644 index 0000000000..1699c84e7c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ResidencyRequirement.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ResidencyRequirement = "us"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReviewDelivery.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewDelivery.ts new file mode 100644 index 0000000000..8fbccd1050 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewDelivery.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReviewDelivery = "inline" | "detached"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts new file mode 100644 index 0000000000..c36533f2ec --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewDelivery } from "./ReviewDelivery"; +import type { ReviewTarget } from "./ReviewTarget"; + +export type ReviewStartParams = { threadId: string, target: ReviewTarget, +/** + * Where to run the review: inline (default) on the current thread or + * detached on a new thread (returned in `reviewThreadId`). + */ +delivery: ReviewDelivery | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartResponse.ts new file mode 100644 index 0000000000..25eb6f82fe --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartResponse.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Turn } from "./Turn"; + +export type ReviewStartResponse = { turn: Turn, +/** + * Identifies the thread where the review runs. + * + * For inline reviews, this is the original thread id. + * For detached reviews, this is the id of the new review thread. + */ +reviewThreadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReviewTarget.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewTarget.ts new file mode 100644 index 0000000000..a79f1e993c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewTarget.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReviewTarget = { "type": "uncommittedChanges" } | { "type": "baseBranch", branch: string, } | { "type": "commit", sha: string, +/** + * Optional human-readable label (e.g., commit subject) for UIs. + */ +title: string | null, } | { "type": "custom", instructions: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SandboxMode.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxMode.ts new file mode 100644 index 0000000000..b8cf4326b9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxMode.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts new file mode 100644 index 0000000000..199d7f2a52 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "../AbsolutePathBuf"; +import type { NetworkAccess } from "./NetworkAccess"; + +export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly" } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SandboxWorkspaceWrite.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxWorkspaceWrite.ts new file mode 100644 index 0000000000..cd19d83f1f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxWorkspaceWrite.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SandboxWorkspaceWrite = { writable_roots: Array, network_access: boolean, exclude_tmpdir_env_var: boolean, exclude_slash_tmp: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SessionSource.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SessionSource.ts new file mode 100644 index 0000000000..b35b421fcd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SessionSource.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SubAgentSource } from "../SubAgentSource"; + +export type SessionSource = "cli" | "vscode" | "exec" | "appServer" | { "subAgent": SubAgentSource } | "unknown"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillDependencies.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillDependencies.ts new file mode 100644 index 0000000000..e2dd4f4241 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillDependencies.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillToolDependency } from "./SkillToolDependency"; + +export type SkillDependencies = { tools: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillErrorInfo.ts new file mode 100644 index 0000000000..6eaf035d8c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillErrorInfo.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillErrorInfo = { path: string, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts new file mode 100644 index 0000000000..86c37a0bd7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillInterface = { displayName?: string, shortDescription?: string, iconSmall?: string, iconLarge?: string, brandColor?: string, defaultPrompt?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillMetadata.ts new file mode 100644 index 0000000000..52c0cd4945 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillMetadata.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillDependencies } from "./SkillDependencies"; +import type { SkillInterface } from "./SkillInterface"; +import type { SkillScope } from "./SkillScope"; + +export type SkillMetadata = { name: string, description: string, +/** + * Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description. + */ +shortDescription?: string, interface?: SkillInterface, dependencies?: SkillDependencies, path: string, scope: SkillScope, enabled: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillScope.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillScope.ts new file mode 100644 index 0000000000..997006f5b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillScope.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillScope = "user" | "repo" | "system" | "admin"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillToolDependency.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillToolDependency.ts new file mode 100644 index 0000000000..a5da45e178 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillToolDependency.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillToolDependency = { type: string, value: string, description?: string, transport?: string, command?: string, url?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteParams.ts new file mode 100644 index 0000000000..5a4bcf9bc0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillsConfigWriteParams = { path: string, enabled: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteResponse.ts new file mode 100644 index 0000000000..c0e8ef7cbd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsConfigWriteResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillsConfigWriteResponse = { effectiveEnabled: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListEntry.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListEntry.ts new file mode 100644 index 0000000000..3f46c98a4a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListEntry.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillErrorInfo } from "./SkillErrorInfo"; +import type { SkillMetadata } from "./SkillMetadata"; + +export type SkillsListEntry = { cwd: string, skills: Array, errors: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListParams.ts new file mode 100644 index 0000000000..d44e6551fd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillsListParams = { +/** + * When empty, defaults to the current session working directory. + */ +cwds?: Array, +/** + * When true, bypass the skills cache and re-scan skills from disk. + */ +forceReload?: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListResponse.ts new file mode 100644 index 0000000000..a27c288a94 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsListResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillsListEntry } from "./SkillsListEntry"; + +export type SkillsListResponse = { data: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TerminalInteractionNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TerminalInteractionNotification.ts new file mode 100644 index 0000000000..1631f86174 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TerminalInteractionNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TerminalInteractionNotification = { threadId: string, turnId: string, itemId: string, processId: string, stdin: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TextElement.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TextElement.ts new file mode 100644 index 0000000000..8841d00499 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TextElement.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ByteRange } from "./ByteRange"; + +export type TextElement = { +/** + * Byte range in the parent `text` buffer that this element occupies. + */ +byteRange: ByteRange, +/** + * Optional human-readable placeholder for the element, displayed in the UI. + */ +placeholder: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TextPosition.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TextPosition.ts new file mode 100644 index 0000000000..e0a6d11a01 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TextPosition.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TextPosition = { +/** + * 1-based line number. + */ +line: number, +/** + * 1-based column number (in Unicode scalar values). + */ +column: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TextRange.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TextRange.ts new file mode 100644 index 0000000000..48b68398f1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TextRange.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextPosition } from "./TextPosition"; + +export type TextRange = { start: TextPosition, end: TextPosition, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts new file mode 100644 index 0000000000..5ef567bd23 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts @@ -0,0 +1,51 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GitInfo } from "./GitInfo"; +import type { SessionSource } from "./SessionSource"; +import type { Turn } from "./Turn"; + +export type Thread = { id: string, +/** + * Usually the first user message in the thread, if available. + */ +preview: string, +/** + * Model provider used for this thread (for example, 'openai'). + */ +modelProvider: string, +/** + * Unix timestamp (in seconds) when the thread was created. + */ +createdAt: number, +/** + * Unix timestamp (in seconds) when the thread was last updated. + */ +updatedAt: number, +/** + * [UNSTABLE] Path to the thread on disk. + */ +path: string | null, +/** + * Working directory captured for the thread. + */ +cwd: string, +/** + * Version of the CLI that created the thread. + */ +cliVersion: string, +/** + * Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.). + */ +source: SessionSource, +/** + * Optional Git metadata captured when the thread was created. + */ +gitInfo: GitInfo | null, +/** + * Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` + * (when `includeTurns` is true) responses. + * For all other responses and notifications returning a Thread, + * the turns field will be an empty list. + */ +turns: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveParams.ts new file mode 100644 index 0000000000..ad4071cbfa --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadArchiveParams = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveResponse.ts new file mode 100644 index 0000000000..b5954268e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArchiveResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadArchiveResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts new file mode 100644 index 0000000000..4ecb1b39df --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts @@ -0,0 +1,26 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "../serde_json/JsonValue"; +import type { AskForApproval } from "./AskForApproval"; +import type { SandboxMode } from "./SandboxMode"; + +/** + * There are two ways to fork a thread: + * 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. + * 2. By path: load the thread from disk by path and fork it into a new thread. + * + * If using path, the thread_id param will be ignored. + * + * Prefer using thread_id whenever possible. + */ +export type ThreadForkParams = { threadId: string, +/** + * [UNSTABLE] Specify the rollout path to fork from. + * If specified, the thread_id param will be ignored. + */ +path: string | null, +/** + * Configuration overrides for the forked thread, if any. + */ +model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts new file mode 100644 index 0000000000..a46480cb7b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "../ReasoningEffort"; +import type { AskForApproval } from "./AskForApproval"; +import type { SandboxPolicy } from "./SandboxPolicy"; +import type { Thread } from "./Thread"; + +export type ThreadForkResponse = { thread: Thread, model: string, modelProvider: string, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts new file mode 100644 index 0000000000..cd2faf65be --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts @@ -0,0 +1,81 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { WebSearchAction } from "../WebSearchAction"; +import type { JsonValue } from "../serde_json/JsonValue"; +import type { CollabAgentState } from "./CollabAgentState"; +import type { CollabAgentTool } from "./CollabAgentTool"; +import type { CollabAgentToolCallStatus } from "./CollabAgentToolCallStatus"; +import type { CommandAction } from "./CommandAction"; +import type { CommandExecutionStatus } from "./CommandExecutionStatus"; +import type { FileUpdateChange } from "./FileUpdateChange"; +import type { McpToolCallError } from "./McpToolCallError"; +import type { McpToolCallResult } from "./McpToolCallResult"; +import type { McpToolCallStatus } from "./McpToolCallStatus"; +import type { PatchApplyStatus } from "./PatchApplyStatus"; +import type { UserInput } from "./UserInput"; + +export type ThreadItem = { "type": "userMessage", id: string, content: Array, } | { "type": "agentMessage", id: string, text: string, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, +/** + * The command to be executed. + */ +command: string, +/** + * The command's working directory. + */ +cwd: string, +/** + * Identifier for the underlying PTY process (when available). + */ +processId: string | null, status: CommandExecutionStatus, +/** + * A best-effort parsing of the command to understand the action(s) it will perform. + * This returns a list of CommandAction objects because a single shell command may + * be composed of many commands piped together. + */ +commandActions: Array, +/** + * The command's output, aggregated from stdout and stderr. + */ +aggregatedOutput: string | null, +/** + * The command's exit code. + */ +exitCode: number | null, +/** + * The duration of the command execution in milliseconds. + */ +durationMs: number | null, } | { "type": "fileChange", id: string, changes: Array, status: PatchApplyStatus, } | { "type": "mcpToolCall", id: string, server: string, tool: string, status: McpToolCallStatus, arguments: JsonValue, result: McpToolCallResult | null, error: McpToolCallError | null, +/** + * The duration of the MCP tool call in milliseconds. + */ +durationMs: number | null, } | { "type": "collabAgentToolCall", +/** + * Unique identifier for this collab tool call. + */ +id: string, +/** + * Name of the collab tool that was invoked. + */ +tool: CollabAgentTool, +/** + * Current status of the collab tool call. + */ +status: CollabAgentToolCallStatus, +/** + * Thread ID of the agent issuing the collab request. + */ +senderThreadId: string, +/** + * Thread ID of the receiving agent, when applicable. In case of spawn operation, + * this corresponds to the newly spawned agent. + */ +receiverThreadIds: Array, +/** + * Prompt text sent as part of the collab tool call, when available. + */ +prompt: string | null, +/** + * Last known status of the target agents, when available. + */ +agentsStates: { [key in string]?: CollabAgentState }, } | { "type": "webSearch", id: string, query: string, action: WebSearchAction | null, } | { "type": "imageView", id: string, path: string, } | { "type": "enteredReviewMode", id: string, review: string, } | { "type": "exitedReviewMode", id: string, review: string, } | { "type": "contextCompaction", id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts new file mode 100644 index 0000000000..357fdd21cd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts @@ -0,0 +1,34 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadSortKey } from "./ThreadSortKey"; +import type { ThreadSourceKind } from "./ThreadSourceKind"; + +export type ThreadListParams = { +/** + * Opaque pagination cursor returned by a previous call. + */ +cursor: string | null, +/** + * Optional page size; defaults to a reasonable server-side value. + */ +limit: number | null, +/** + * Optional sort key; defaults to created_at. + */ +sortKey: ThreadSortKey | null, +/** + * Optional provider filter; when set, only sessions recorded under these + * providers are returned. When present but empty, includes all providers. + */ +modelProviders: Array | null, +/** + * Optional source filter; when set, only sessions from these source kinds + * are returned. When omitted or empty, defaults to interactive sources. + */ +sourceKinds: Array | null, +/** + * Optional archived filter; when set to true, only archived threads are returned. + * If false or null, only non-archived threads are returned. + */ +archived: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListResponse.ts new file mode 100644 index 0000000000..3c0296e5e0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListResponse.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Thread } from "./Thread"; + +export type ThreadListResponse = { data: Array, +/** + * Opaque cursor to pass to the next call to continue after the last item. + * if None, there are no more items to return. + */ +nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts new file mode 100644 index 0000000000..5419b00bd1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadLoadedListParams = { +/** + * Opaque pagination cursor returned by a previous call. + */ +cursor: string | null, +/** + * Optional page size; defaults to no limit. + */ +limit: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListResponse.ts new file mode 100644 index 0000000000..d215a45d01 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListResponse.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadLoadedListResponse = { +/** + * Thread ids for sessions currently loaded in memory. + */ +data: Array, +/** + * Opaque cursor to pass to the next call to continue after the last item. + * if None, there are no more items to return. + */ +nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadNameUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadNameUpdatedNotification.ts new file mode 100644 index 0000000000..c944b5aae3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadNameUpdatedNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadNameUpdatedNotification = { threadId: string, threadName?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadParams.ts new file mode 100644 index 0000000000..b274d1e774 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadParams.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadReadParams = { threadId: string, +/** + * When true, include turns and their items from rollout history. + */ +includeTurns: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadResponse.ts new file mode 100644 index 0000000000..a6da50649c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadReadResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Thread } from "./Thread"; + +export type ThreadReadResponse = { thread: Thread, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts new file mode 100644 index 0000000000..dca685aa47 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts @@ -0,0 +1,36 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Personality } from "../Personality"; +import type { ResponseItem } from "../ResponseItem"; +import type { JsonValue } from "../serde_json/JsonValue"; +import type { AskForApproval } from "./AskForApproval"; +import type { SandboxMode } from "./SandboxMode"; + +/** + * There are three ways to resume a thread: + * 1. By thread_id: load the thread from disk by thread_id and resume it. + * 2. By history: instantiate the thread from memory and resume it. + * 3. By path: load the thread from disk by path and resume it. + * + * The precedence is: history > path > thread_id. + * If using history or path, the thread_id param will be ignored. + * + * Prefer using thread_id whenever possible. + */ +export type ThreadResumeParams = { threadId: string, +/** + * [UNSTABLE] FOR CODEX CLOUD - DO NOT USE. + * If specified, the thread will be resumed with the provided history + * instead of loaded from disk. + */ +history: Array | null, +/** + * [UNSTABLE] Specify the rollout path to resume from. + * If specified, the thread_id param will be ignored. + */ +path: string | null, +/** + * Configuration overrides for the resumed thread, if any. + */ +model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, personality: Personality | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts new file mode 100644 index 0000000000..6d7a70a6a9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "../ReasoningEffort"; +import type { AskForApproval } from "./AskForApproval"; +import type { SandboxPolicy } from "./SandboxPolicy"; +import type { Thread } from "./Thread"; + +export type ThreadResumeResponse = { thread: Thread, model: string, modelProvider: string, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackParams.ts new file mode 100644 index 0000000000..b867978202 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackParams.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadRollbackParams = { threadId: string, +/** + * The number of turns to drop from the end of the thread. Must be >= 1. + * + * This only modifies the thread's history and does not revert local file changes + * that have been made by the agent. Clients are responsible for reverting these changes. + */ +numTurns: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackResponse.ts new file mode 100644 index 0000000000..1f88f17630 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadRollbackResponse.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Thread } from "./Thread"; + +export type ThreadRollbackResponse = { +/** + * The updated thread after applying the rollback, with `turns` populated. + * + * The ThreadItems stored in each Turn are lossy since we explicitly do not + * persist all agent interactions, such as command executions. This is the same + * behavior as `thread/resume`. + */ +thread: Thread, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameParams.ts new file mode 100644 index 0000000000..82b9b3a1c6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadSetNameParams = { threadId: string, name: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameResponse.ts new file mode 100644 index 0000000000..09143d251c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSetNameResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadSetNameResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSortKey.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSortKey.ts new file mode 100644 index 0000000000..dbf1b6c40f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSortKey.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadSortKey = "created_at" | "updated_at"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSourceKind.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSourceKind.ts new file mode 100644 index 0000000000..0a464e3d8d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadSourceKind.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadSourceKind = "cli" | "vscode" | "exec" | "appServer" | "subAgent" | "subAgentReview" | "subAgentCompact" | "subAgentThreadSpawn" | "subAgentOther" | "unknown"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts new file mode 100644 index 0000000000..bb7316706e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Personality } from "../Personality"; +import type { JsonValue } from "../serde_json/JsonValue"; +import type { AskForApproval } from "./AskForApproval"; +import type { DynamicToolSpec } from "./DynamicToolSpec"; +import type { SandboxMode } from "./SandboxMode"; + +export type ThreadStartParams = { model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, personality: Personality | null, ephemeral: boolean | null, dynamicTools: Array | null, +/** + * If true, opt into emitting raw response items on the event stream. + * + * This is for internal use only (e.g. Codex Cloud). + * (TODO): Figure out a better way to categorize internal / experimental events & protocols. + */ +experimentalRawEvents: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts new file mode 100644 index 0000000000..4a76f9af20 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReasoningEffort } from "../ReasoningEffort"; +import type { AskForApproval } from "./AskForApproval"; +import type { SandboxPolicy } from "./SandboxPolicy"; +import type { Thread } from "./Thread"; + +export type ThreadStartResponse = { thread: Thread, model: string, modelProvider: string, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartedNotification.ts new file mode 100644 index 0000000000..83be55772e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Thread } from "./Thread"; + +export type ThreadStartedNotification = { thread: Thread, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsage.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsage.ts new file mode 100644 index 0000000000..b452c408e2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsage.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TokenUsageBreakdown } from "./TokenUsageBreakdown"; + +export type ThreadTokenUsage = { total: TokenUsageBreakdown, last: TokenUsageBreakdown, modelContextWindow: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsageUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsageUpdatedNotification.ts new file mode 100644 index 0000000000..1be282500c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsageUpdatedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadTokenUsage } from "./ThreadTokenUsage"; + +export type ThreadTokenUsageUpdatedNotification = { threadId: string, turnId: string, tokenUsage: ThreadTokenUsage, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveParams.ts new file mode 100644 index 0000000000..4e464989e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadUnarchiveParams = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveResponse.ts new file mode 100644 index 0000000000..96ea5dcdc7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadUnarchiveResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Thread } from "./Thread"; + +export type ThreadUnarchiveResponse = { thread: Thread, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TokenUsageBreakdown.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TokenUsageBreakdown.ts new file mode 100644 index 0000000000..1d4e408fad --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TokenUsageBreakdown.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TokenUsageBreakdown = { totalTokens: number, inputTokens: number, cachedInputTokens: number, outputTokens: number, reasoningOutputTokens: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputAnswer.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputAnswer.ts new file mode 100644 index 0000000000..0c912db044 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputAnswer.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * EXPERIMENTAL. Captures a user's answer to a request_user_input question. + */ +export type ToolRequestUserInputAnswer = { answers: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputOption.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputOption.ts new file mode 100644 index 0000000000..ab21aca046 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputOption.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * EXPERIMENTAL. Defines a single selectable option for request_user_input. + */ +export type ToolRequestUserInputOption = { label: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts new file mode 100644 index 0000000000..bee81cb8e2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestion"; + +/** + * EXPERIMENTAL. Params sent with a request_user_input event. + */ +export type ToolRequestUserInputParams = { threadId: string, turnId: string, itemId: string, questions: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputQuestion.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputQuestion.ts new file mode 100644 index 0000000000..1afc4e47ba --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputQuestion.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ToolRequestUserInputOption } from "./ToolRequestUserInputOption"; + +/** + * EXPERIMENTAL. Represents one request_user_input question and its required options. + */ +export type ToolRequestUserInputQuestion = { id: string, header: string, question: string, isOther: boolean, isSecret: boolean, options: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputResponse.ts new file mode 100644 index 0000000000..e4dd8bbca9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputResponse.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ToolRequestUserInputAnswer } from "./ToolRequestUserInputAnswer"; + +/** + * EXPERIMENTAL. Response payload mapping question ids to answers. + */ +export type ToolRequestUserInputResponse = { answers: { [key in string]?: ToolRequestUserInputAnswer }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ToolsV2.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ToolsV2.ts new file mode 100644 index 0000000000..0b1bee5146 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ToolsV2.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ToolsV2 = { web_search: boolean | null, view_image: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts new file mode 100644 index 0000000000..709ed5ccbe --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadItem } from "./ThreadItem"; +import type { TurnError } from "./TurnError"; +import type { TurnStatus } from "./TurnStatus"; + +export type Turn = { id: string, +/** + * Only populated on a `thread/resume` or `thread/fork` response. + * For all other responses and notifications returning a Turn, + * the items field will be an empty list. + */ +items: Array, status: TurnStatus, +/** + * Only populated when the Turn's status is failed. + */ +error: TurnError | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnCompletedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnCompletedNotification.ts new file mode 100644 index 0000000000..e1b151bfa7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnCompletedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Turn } from "./Turn"; + +export type TurnCompletedNotification = { threadId: string, turn: Turn, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnDiffUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnDiffUpdatedNotification.ts new file mode 100644 index 0000000000..ec2b33349d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnDiffUpdatedNotification.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Notification that the turn-level unified diff has changed. + * Contains the latest aggregated diff across all file changes in the turn. + */ +export type TurnDiffUpdatedNotification = { threadId: string, turnId: string, diff: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts new file mode 100644 index 0000000000..765a8e050b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CodexErrorInfo } from "./CodexErrorInfo"; + +export type TurnError = { message: string, codexErrorInfo: CodexErrorInfo | null, additionalDetails: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptParams.ts new file mode 100644 index 0000000000..ec35689e6d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnInterruptParams = { threadId: string, turnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptResponse.ts new file mode 100644 index 0000000000..7ce6e35bd6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnInterruptResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnInterruptResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStep.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStep.ts new file mode 100644 index 0000000000..22d1fbb6b3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStep.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnPlanStepStatus } from "./TurnPlanStepStatus"; + +export type TurnPlanStep = { step: string, status: TurnPlanStepStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStepStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStepStatus.ts new file mode 100644 index 0000000000..f6733a6885 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanStepStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnPlanStepStatus = "pending" | "inProgress" | "completed"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanUpdatedNotification.ts new file mode 100644 index 0000000000..ed13cb4a23 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPlanUpdatedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnPlanStep } from "./TurnPlanStep"; + +export type TurnPlanUpdatedNotification = { threadId: string, turnId: string, explanation: string | null, plan: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts new file mode 100644 index 0000000000..4987c9e5c1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts @@ -0,0 +1,50 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CollaborationMode } from "../CollaborationMode"; +import type { Personality } from "../Personality"; +import type { ReasoningEffort } from "../ReasoningEffort"; +import type { ReasoningSummary } from "../ReasoningSummary"; +import type { JsonValue } from "../serde_json/JsonValue"; +import type { AskForApproval } from "./AskForApproval"; +import type { SandboxPolicy } from "./SandboxPolicy"; +import type { UserInput } from "./UserInput"; + +export type TurnStartParams = { threadId: string, input: Array, +/** + * Override the working directory for this turn and subsequent turns. + */ +cwd: string | null, +/** + * Override the approval policy for this turn and subsequent turns. + */ +approvalPolicy: AskForApproval | null, +/** + * Override the sandbox policy for this turn and subsequent turns. + */ +sandboxPolicy: SandboxPolicy | null, +/** + * Override the model for this turn and subsequent turns. + */ +model: string | null, +/** + * Override the reasoning effort for this turn and subsequent turns. + */ +effort: ReasoningEffort | null, +/** + * Override the reasoning summary for this turn and subsequent turns. + */ +summary: ReasoningSummary | null, +/** + * Override the personality for this turn and subsequent turns. + */ +personality: Personality | null, +/** + * Optional JSON Schema used to constrain the final assistant message for this turn. + */ +outputSchema: JsonValue | null, +/** + * EXPERIMENTAL - set a pre-set collaboration mode. + * Takes precedence over model, reasoning_effort, and developer instructions if set. + */ +collaborationMode: CollaborationMode | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartResponse.ts new file mode 100644 index 0000000000..cc2ee3772a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Turn } from "./Turn"; + +export type TurnStartResponse = { turn: Turn, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartedNotification.ts new file mode 100644 index 0000000000..34f71b2465 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Turn } from "./Turn"; + +export type TurnStartedNotification = { threadId: string, turn: Turn, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStatus.ts new file mode 100644 index 0000000000..476922edc2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnStatus = "completed" | "interrupted" | "failed" | "inProgress"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts b/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts new file mode 100644 index 0000000000..65196fe5d9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextElement } from "./TextElement"; + +export type UserInput = { "type": "text", text: string, +/** + * UI-defined spans within `text` used to render or persist special elements. + */ +text_elements: Array, } | { "type": "image", url: string, } | { "type": "localImage", path: string, } | { "type": "skill", name: string, path: string, } | { "type": "mention", name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WindowsWorldWritableWarningNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WindowsWorldWritableWarningNotification.ts new file mode 100644 index 0000000000..a11e7cef49 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/WindowsWorldWritableWarningNotification.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WindowsWorldWritableWarningNotification = { samplePaths: Array, extraCount: number, failedScan: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WriteStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WriteStatus.ts new file mode 100644 index 0000000000..068eb3bdb9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/WriteStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WriteStatus = "ok" | "okOverridden"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts new file mode 100644 index 0000000000..4f19e3e902 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -0,0 +1,175 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +export type { Account } from "./Account"; +export type { AccountLoginCompletedNotification } from "./AccountLoginCompletedNotification"; +export type { AccountRateLimitsUpdatedNotification } from "./AccountRateLimitsUpdatedNotification"; +export type { AccountUpdatedNotification } from "./AccountUpdatedNotification"; +export type { AgentMessageDeltaNotification } from "./AgentMessageDeltaNotification"; +export type { AnalyticsConfig } from "./AnalyticsConfig"; +export type { AppInfo } from "./AppInfo"; +export type { AppsListParams } from "./AppsListParams"; +export type { AppsListResponse } from "./AppsListResponse"; +export type { AskForApproval } from "./AskForApproval"; +export type { ByteRange } from "./ByteRange"; +export type { CancelLoginAccountParams } from "./CancelLoginAccountParams"; +export type { CancelLoginAccountResponse } from "./CancelLoginAccountResponse"; +export type { CancelLoginAccountStatus } from "./CancelLoginAccountStatus"; +export type { ChatgptAuthTokensRefreshParams } from "./ChatgptAuthTokensRefreshParams"; +export type { ChatgptAuthTokensRefreshReason } from "./ChatgptAuthTokensRefreshReason"; +export type { ChatgptAuthTokensRefreshResponse } from "./ChatgptAuthTokensRefreshResponse"; +export type { CodexErrorInfo } from "./CodexErrorInfo"; +export type { CollabAgentState } from "./CollabAgentState"; +export type { CollabAgentStatus } from "./CollabAgentStatus"; +export type { CollabAgentTool } from "./CollabAgentTool"; +export type { CollabAgentToolCallStatus } from "./CollabAgentToolCallStatus"; +export type { CollaborationModeListParams } from "./CollaborationModeListParams"; +export type { CollaborationModeListResponse } from "./CollaborationModeListResponse"; +export type { CommandAction } from "./CommandAction"; +export type { CommandExecParams } from "./CommandExecParams"; +export type { CommandExecResponse } from "./CommandExecResponse"; +export type { CommandExecutionApprovalDecision } from "./CommandExecutionApprovalDecision"; +export type { CommandExecutionOutputDeltaNotification } from "./CommandExecutionOutputDeltaNotification"; +export type { CommandExecutionRequestApprovalParams } from "./CommandExecutionRequestApprovalParams"; +export type { CommandExecutionRequestApprovalResponse } from "./CommandExecutionRequestApprovalResponse"; +export type { CommandExecutionStatus } from "./CommandExecutionStatus"; +export type { Config } from "./Config"; +export type { ConfigBatchWriteParams } from "./ConfigBatchWriteParams"; +export type { ConfigEdit } from "./ConfigEdit"; +export type { ConfigLayer } from "./ConfigLayer"; +export type { ConfigLayerMetadata } from "./ConfigLayerMetadata"; +export type { ConfigLayerSource } from "./ConfigLayerSource"; +export type { ConfigReadParams } from "./ConfigReadParams"; +export type { ConfigReadResponse } from "./ConfigReadResponse"; +export type { ConfigRequirements } from "./ConfigRequirements"; +export type { ConfigRequirementsReadResponse } from "./ConfigRequirementsReadResponse"; +export type { ConfigValueWriteParams } from "./ConfigValueWriteParams"; +export type { ConfigWarningNotification } from "./ConfigWarningNotification"; +export type { ConfigWriteResponse } from "./ConfigWriteResponse"; +export type { ContextCompactedNotification } from "./ContextCompactedNotification"; +export type { CreditsSnapshot } from "./CreditsSnapshot"; +export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification"; +export type { DynamicToolCallParams } from "./DynamicToolCallParams"; +export type { DynamicToolCallResponse } from "./DynamicToolCallResponse"; +export type { DynamicToolSpec } from "./DynamicToolSpec"; +export type { ErrorNotification } from "./ErrorNotification"; +export type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; +export type { FeedbackUploadParams } from "./FeedbackUploadParams"; +export type { FeedbackUploadResponse } from "./FeedbackUploadResponse"; +export type { FileChangeApprovalDecision } from "./FileChangeApprovalDecision"; +export type { FileChangeOutputDeltaNotification } from "./FileChangeOutputDeltaNotification"; +export type { FileChangeRequestApprovalParams } from "./FileChangeRequestApprovalParams"; +export type { FileChangeRequestApprovalResponse } from "./FileChangeRequestApprovalResponse"; +export type { FileUpdateChange } from "./FileUpdateChange"; +export type { GetAccountParams } from "./GetAccountParams"; +export type { GetAccountRateLimitsResponse } from "./GetAccountRateLimitsResponse"; +export type { GetAccountResponse } from "./GetAccountResponse"; +export type { GitInfo } from "./GitInfo"; +export type { ItemCompletedNotification } from "./ItemCompletedNotification"; +export type { ItemStartedNotification } from "./ItemStartedNotification"; +export type { ListMcpServerStatusParams } from "./ListMcpServerStatusParams"; +export type { ListMcpServerStatusResponse } from "./ListMcpServerStatusResponse"; +export type { LoginAccountParams } from "./LoginAccountParams"; +export type { LoginAccountResponse } from "./LoginAccountResponse"; +export type { LogoutAccountResponse } from "./LogoutAccountResponse"; +export type { McpAuthStatus } from "./McpAuthStatus"; +export type { McpServerOauthLoginCompletedNotification } from "./McpServerOauthLoginCompletedNotification"; +export type { McpServerOauthLoginParams } from "./McpServerOauthLoginParams"; +export type { McpServerOauthLoginResponse } from "./McpServerOauthLoginResponse"; +export type { McpServerRefreshResponse } from "./McpServerRefreshResponse"; +export type { McpServerStatus } from "./McpServerStatus"; +export type { McpToolCallError } from "./McpToolCallError"; +export type { McpToolCallProgressNotification } from "./McpToolCallProgressNotification"; +export type { McpToolCallResult } from "./McpToolCallResult"; +export type { McpToolCallStatus } from "./McpToolCallStatus"; +export type { MergeStrategy } from "./MergeStrategy"; +export type { Model } from "./Model"; +export type { ModelListParams } from "./ModelListParams"; +export type { ModelListResponse } from "./ModelListResponse"; +export type { NetworkAccess } from "./NetworkAccess"; +export type { OverriddenMetadata } from "./OverriddenMetadata"; +export type { PatchApplyStatus } from "./PatchApplyStatus"; +export type { PatchChangeKind } from "./PatchChangeKind"; +export type { PlanDeltaNotification } from "./PlanDeltaNotification"; +export type { ProfileV2 } from "./ProfileV2"; +export type { RateLimitSnapshot } from "./RateLimitSnapshot"; +export type { RateLimitWindow } from "./RateLimitWindow"; +export type { RawResponseItemCompletedNotification } from "./RawResponseItemCompletedNotification"; +export type { ReasoningEffortOption } from "./ReasoningEffortOption"; +export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPartAddedNotification"; +export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification"; +export type { ReasoningTextDeltaNotification } from "./ReasoningTextDeltaNotification"; +export type { ResidencyRequirement } from "./ResidencyRequirement"; +export type { ReviewDelivery } from "./ReviewDelivery"; +export type { ReviewStartParams } from "./ReviewStartParams"; +export type { ReviewStartResponse } from "./ReviewStartResponse"; +export type { ReviewTarget } from "./ReviewTarget"; +export type { SandboxMode } from "./SandboxMode"; +export type { SandboxPolicy } from "./SandboxPolicy"; +export type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite"; +export type { SessionSource } from "./SessionSource"; +export type { SkillDependencies } from "./SkillDependencies"; +export type { SkillErrorInfo } from "./SkillErrorInfo"; +export type { SkillInterface } from "./SkillInterface"; +export type { SkillMetadata } from "./SkillMetadata"; +export type { SkillScope } from "./SkillScope"; +export type { SkillToolDependency } from "./SkillToolDependency"; +export type { SkillsConfigWriteParams } from "./SkillsConfigWriteParams"; +export type { SkillsConfigWriteResponse } from "./SkillsConfigWriteResponse"; +export type { SkillsListEntry } from "./SkillsListEntry"; +export type { SkillsListParams } from "./SkillsListParams"; +export type { SkillsListResponse } from "./SkillsListResponse"; +export type { TerminalInteractionNotification } from "./TerminalInteractionNotification"; +export type { TextElement } from "./TextElement"; +export type { TextPosition } from "./TextPosition"; +export type { TextRange } from "./TextRange"; +export type { Thread } from "./Thread"; +export type { ThreadArchiveParams } from "./ThreadArchiveParams"; +export type { ThreadArchiveResponse } from "./ThreadArchiveResponse"; +export type { ThreadForkParams } from "./ThreadForkParams"; +export type { ThreadForkResponse } from "./ThreadForkResponse"; +export type { ThreadItem } from "./ThreadItem"; +export type { ThreadListParams } from "./ThreadListParams"; +export type { ThreadListResponse } from "./ThreadListResponse"; +export type { ThreadLoadedListParams } from "./ThreadLoadedListParams"; +export type { ThreadLoadedListResponse } from "./ThreadLoadedListResponse"; +export type { ThreadNameUpdatedNotification } from "./ThreadNameUpdatedNotification"; +export type { ThreadReadParams } from "./ThreadReadParams"; +export type { ThreadReadResponse } from "./ThreadReadResponse"; +export type { ThreadResumeParams } from "./ThreadResumeParams"; +export type { ThreadResumeResponse } from "./ThreadResumeResponse"; +export type { ThreadRollbackParams } from "./ThreadRollbackParams"; +export type { ThreadRollbackResponse } from "./ThreadRollbackResponse"; +export type { ThreadSetNameParams } from "./ThreadSetNameParams"; +export type { ThreadSetNameResponse } from "./ThreadSetNameResponse"; +export type { ThreadSortKey } from "./ThreadSortKey"; +export type { ThreadSourceKind } from "./ThreadSourceKind"; +export type { ThreadStartParams } from "./ThreadStartParams"; +export type { ThreadStartResponse } from "./ThreadStartResponse"; +export type { ThreadStartedNotification } from "./ThreadStartedNotification"; +export type { ThreadTokenUsage } from "./ThreadTokenUsage"; +export type { ThreadTokenUsageUpdatedNotification } from "./ThreadTokenUsageUpdatedNotification"; +export type { ThreadUnarchiveParams } from "./ThreadUnarchiveParams"; +export type { ThreadUnarchiveResponse } from "./ThreadUnarchiveResponse"; +export type { TokenUsageBreakdown } from "./TokenUsageBreakdown"; +export type { ToolRequestUserInputAnswer } from "./ToolRequestUserInputAnswer"; +export type { ToolRequestUserInputOption } from "./ToolRequestUserInputOption"; +export type { ToolRequestUserInputParams } from "./ToolRequestUserInputParams"; +export type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestion"; +export type { ToolRequestUserInputResponse } from "./ToolRequestUserInputResponse"; +export type { ToolsV2 } from "./ToolsV2"; +export type { Turn } from "./Turn"; +export type { TurnCompletedNotification } from "./TurnCompletedNotification"; +export type { TurnDiffUpdatedNotification } from "./TurnDiffUpdatedNotification"; +export type { TurnError } from "./TurnError"; +export type { TurnInterruptParams } from "./TurnInterruptParams"; +export type { TurnInterruptResponse } from "./TurnInterruptResponse"; +export type { TurnPlanStep } from "./TurnPlanStep"; +export type { TurnPlanStepStatus } from "./TurnPlanStepStatus"; +export type { TurnPlanUpdatedNotification } from "./TurnPlanUpdatedNotification"; +export type { TurnStartParams } from "./TurnStartParams"; +export type { TurnStartResponse } from "./TurnStartResponse"; +export type { TurnStartedNotification } from "./TurnStartedNotification"; +export type { TurnStatus } from "./TurnStatus"; +export type { UserInput } from "./UserInput"; +export type { WindowsWorldWritableWarningNotification } from "./WindowsWorldWritableWarningNotification"; +export type { WriteStatus } from "./WriteStatus"; diff --git a/codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs b/codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs new file mode 100644 index 0000000000..a8e36d1ee8 --- /dev/null +++ b/codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs @@ -0,0 +1,32 @@ +use anyhow::Context; +use anyhow::Result; +use clap::Parser; +use std::path::PathBuf; + +#[derive(Parser, Debug)] +#[command(about = "Regenerate vendored app-server schema fixtures")] +struct Args { + /// Root directory containing `typescript/` and `json/`. + #[arg(long = "schema-root", value_name = "DIR")] + schema_root: Option, + + /// Optional path to the Prettier executable to format generated TypeScript files. + #[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")] + prettier: Option, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let schema_root = args + .schema_root + .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("schema")); + + codex_app_server_protocol::write_schema_fixtures(&schema_root, args.prettier.as_deref()) + .with_context(|| { + format!( + "failed to regenerate schema fixtures under {}", + schema_root.display() + ) + }) +} diff --git a/codex-rs/app-server-protocol/src/lib.rs b/codex-rs/app-server-protocol/src/lib.rs index 06102083f4..adb96a186e 100644 --- a/codex-rs/app-server-protocol/src/lib.rs +++ b/codex-rs/app-server-protocol/src/lib.rs @@ -1,6 +1,7 @@ mod export; mod jsonrpc_lite; mod protocol; +mod schema_fixtures; pub use export::generate_json; pub use export::generate_ts; @@ -10,3 +11,5 @@ pub use protocol::common::*; pub use protocol::thread_history::*; pub use protocol::v1::*; pub use protocol::v2::*; +pub use schema_fixtures::read_schema_fixture_tree; +pub use schema_fixtures::write_schema_fixtures; diff --git a/codex-rs/app-server-protocol/src/schema_fixtures.rs b/codex-rs/app-server-protocol/src/schema_fixtures.rs new file mode 100644 index 0000000000..efd8bb2cb0 --- /dev/null +++ b/codex-rs/app-server-protocol/src/schema_fixtures.rs @@ -0,0 +1,127 @@ +use anyhow::Context; +use anyhow::Result; +use serde_json::Map; +use serde_json::Value; +use std::collections::BTreeMap; +use std::path::Path; +use std::path::PathBuf; + +pub fn read_schema_fixture_tree(schema_root: &Path) -> Result>> { + let typescript_root = schema_root.join("typescript"); + let json_root = schema_root.join("json"); + + let mut all = BTreeMap::new(); + for (rel, bytes) in collect_files_recursive(&typescript_root)? { + all.insert(PathBuf::from("typescript").join(rel), bytes); + } + for (rel, bytes) in collect_files_recursive(&json_root)? { + all.insert(PathBuf::from("json").join(rel), bytes); + } + + Ok(all) +} + +/// Regenerates `schema/typescript/` and `schema/json/`. +/// +/// This is intended to be used by tooling (e.g., `just write-app-server-schema`). +/// It deletes any previously generated files so stale artifacts are removed. +pub fn write_schema_fixtures(schema_root: &Path, prettier: Option<&Path>) -> Result<()> { + let typescript_out_dir = schema_root.join("typescript"); + let json_out_dir = schema_root.join("json"); + + ensure_empty_dir(&typescript_out_dir)?; + ensure_empty_dir(&json_out_dir)?; + + crate::generate_ts(&typescript_out_dir, prettier)?; + crate::generate_json(&json_out_dir)?; + + Ok(()) +} + +fn ensure_empty_dir(dir: &Path) -> Result<()> { + if dir.exists() { + std::fs::remove_dir_all(dir) + .with_context(|| format!("failed to remove {}", dir.display()))?; + } + std::fs::create_dir_all(dir).with_context(|| format!("failed to create {}", dir.display()))?; + Ok(()) +} + +fn read_file_bytes(path: &Path) -> Result> { + let bytes = + std::fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; + if path.extension().is_some_and(|ext| ext == "json") { + let value: Value = serde_json::from_slice(&bytes) + .with_context(|| format!("failed to parse JSON in {}", path.display()))?; + let value = canonicalize_json(&value); + let normalized = serde_json::to_vec_pretty(&value) + .with_context(|| format!("failed to reserialize JSON in {}", path.display()))?; + return Ok(normalized); + } + if path.extension().is_some_and(|ext| ext == "ts") { + // Windows checkouts (and some generators) may produce CRLF; normalize so the + // fixture test is platform-independent. + let text = String::from_utf8(bytes) + .with_context(|| format!("expected UTF-8 TypeScript in {}", path.display()))?; + let text = text.replace("\r\n", "\n").replace('\r', "\n"); + return Ok(text.into_bytes()); + } + Ok(bytes) +} + +fn canonicalize_json(value: &Value) -> Value { + match value { + Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()), + Value::Object(map) => { + let mut entries: Vec<_> = map.iter().collect(); + entries.sort_by(|(left, _), (right, _)| left.cmp(right)); + let mut sorted = Map::with_capacity(map.len()); + for (key, child) in entries { + sorted.insert(key.clone(), canonicalize_json(child)); + } + Value::Object(sorted) + } + _ => value.clone(), + } +} + +fn collect_files_recursive(root: &Path) -> Result>> { + let mut files = BTreeMap::new(); + + let mut stack = vec![root.to_path_buf()]; + while let Some(dir) = stack.pop() { + for entry in std::fs::read_dir(&dir) + .with_context(|| format!("failed to read dir {}", dir.display()))? + { + let entry = + entry.with_context(|| format!("failed to read dir entry in {}", dir.display()))?; + let path = entry.path(); + // On some platforms, Bazel runfiles are symlinks. `DirEntry::file_type()` does not + // follow symlinks, so use `metadata()` here to treat symlinks as the files/dirs they + // point to. + let metadata = std::fs::metadata(&path) + .with_context(|| format!("failed to stat {}", path.display()))?; + if metadata.is_dir() { + stack.push(path); + continue; + } else if !metadata.is_file() { + continue; + } + + let rel = path + .strip_prefix(root) + .with_context(|| { + format!( + "failed to strip prefix {} from {}", + root.display(), + path.display() + ) + })? + .to_path_buf(); + + files.insert(rel, read_file_bytes(&path)?); + } + } + + Ok(files) +} diff --git a/codex-rs/app-server-protocol/tests/schema_fixtures.rs b/codex-rs/app-server-protocol/tests/schema_fixtures.rs new file mode 100644 index 0000000000..12379f7809 --- /dev/null +++ b/codex-rs/app-server-protocol/tests/schema_fixtures.rs @@ -0,0 +1,97 @@ +use anyhow::Context; +use anyhow::Result; +use codex_app_server_protocol::read_schema_fixture_tree; +use codex_app_server_protocol::write_schema_fixtures; +use similar::TextDiff; +use std::path::Path; + +#[test] +fn schema_fixtures_match_generated() -> Result<()> { + let schema_root = schema_root()?; + let fixture_tree = read_tree(&schema_root)?; + + let temp_dir = tempfile::tempdir().context("create temp dir")?; + write_schema_fixtures(temp_dir.path(), None).context("generate schema fixtures")?; + let generated_tree = read_tree(temp_dir.path())?; + + let fixture_paths = fixture_tree + .keys() + .map(|p| p.display().to_string()) + .collect::>(); + let generated_paths = generated_tree + .keys() + .map(|p| p.display().to_string()) + .collect::>(); + + if fixture_paths != generated_paths { + let expected = fixture_paths.join("\n"); + let actual = generated_paths.join("\n"); + let diff = TextDiff::from_lines(&expected, &actual) + .unified_diff() + .header("fixture", "generated") + .to_string(); + + panic!( + "Vendored app-server schema fixture file set doesn't match freshly generated output. \ +Run `just write-app-server-schema` to overwrite with your changes.\n\n{diff}" + ); + } + + // If the file sets match, diff contents for each file for a nicer error. + for (path, expected) in &fixture_tree { + let actual = generated_tree + .get(path) + .ok_or_else(|| anyhow::anyhow!("missing generated file: {}", path.display()))?; + + if expected == actual { + continue; + } + + let expected_str = String::from_utf8_lossy(expected); + let actual_str = String::from_utf8_lossy(actual); + let diff = TextDiff::from_lines(&expected_str, &actual_str) + .unified_diff() + .header("fixture", "generated") + .to_string(); + panic!( + "Vendored app-server schema fixture {} differs from generated output. \ +Run `just write-app-server-schema` to overwrite with your changes.\n\n{diff}", + path.display() + ); + } + + Ok(()) +} + +fn schema_root() -> Result { + // In Bazel runfiles (especially manifest-only mode), resolving directories is not + // reliable. Resolve a known file, then walk up to the schema root. + let typescript_index = codex_utils_cargo_bin::find_resource!("schema/typescript/index.ts") + .context("resolve TypeScript schema index.ts")?; + let schema_root = typescript_index + .parent() + .and_then(|p| p.parent()) + .context("derive schema root from schema/typescript/index.ts")? + .to_path_buf(); + + // Sanity check that the JSON fixtures resolve to the same schema root. + let json_bundle = + codex_utils_cargo_bin::find_resource!("schema/json/codex_app_server_protocol.schemas.json") + .context("resolve JSON schema bundle")?; + let json_root = json_bundle + .parent() + .and_then(|p| p.parent()) + .context("derive schema root from schema/json/codex_app_server_protocol.schemas.json")?; + anyhow::ensure!( + schema_root == json_root, + "schema roots disagree: typescript={} json={}", + schema_root.display(), + json_root.display() + ); + + Ok(schema_root) +} + +fn read_tree(root: &Path) -> Result>> { + read_schema_fixture_tree(root).context("read schema fixture tree") +} diff --git a/justfile b/justfile index 8fb5a7d941..67577c7c7f 100644 --- a/justfile +++ b/justfile @@ -68,6 +68,10 @@ mcp-server-run *args: write-config-schema: cargo run -p codex-core --bin codex-write-config-schema +# Regenerate vendored app-server protocol schema artifacts. +write-app-server-schema: + cargo run -p codex-app-server-protocol --bin write_schema_fixtures + # Tail logs from the state SQLite database log *args: if [ "${1:-}" = "--" ]; then shift; fi; cargo run -p codex-state --bin logs_client -- "$@" From 1644cbfc6dc23e08c18f0d851dca4f3f0f5e7434 Mon Sep 17 00:00:00 2001 From: pap-openai Date: Mon, 2 Feb 2026 09:13:17 +0100 Subject: [PATCH 063/139] Session picker shows thread_name if set (#10340) - shows names of threads in the ResumePicker used by `/resume` and `codex resume` if set, default to preview (previous behaviour) if none - adds a `find_thread_names_by_ids` that maps names to IDs in `codex-rs/core/src/rollout/session_index.rs`. It reads sequentially in normal (instead of reverse order in `codex resume `) the index mapping file. This function is called from a list of session (default page is 25, pages loaded depends of height of terminal), for which most of them will always have at least one session unnamed and require the whole file to be read therefore. Could be better and sqlite integration will make this better - those reads won't be needed when leveraging sqlite Opened questions: - We could rename the TUI "Conversation" column to "Name" or "Thread" that would feel more accurate. Could be a fast-follow if we implement auto-naming as it'll always be a name instead? --- codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/rollout/session_index.rs | 75 ++++++ codex-rs/tui/src/resume_picker.rs | 226 ++++++++++++++++-- ...er__tests__resume_picker_thread_names.snap | 8 + 4 files changed, 294 insertions(+), 16 deletions(-) create mode 100644 codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 94ba2f26b0..e11b81e6fc 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -120,6 +120,7 @@ pub use rollout::list::parse_cursor; pub use rollout::list::read_head_for_summary; pub use rollout::list::read_session_meta_line; pub use rollout::rollout_date_parts; +pub use rollout::session_index::find_thread_names_by_ids; pub use transport_manager::TransportManager; mod function_tool; mod state; diff --git a/codex-rs/core/src/rollout/session_index.rs b/codex-rs/core/src/rollout/session_index.rs index 98f7e35d84..c546dca331 100644 --- a/codex-rs/core/src/rollout/session_index.rs +++ b/codex-rs/core/src/rollout/session_index.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; +use std::collections::HashSet; use std::fs::File; use std::io::Read; use std::io::Seek; @@ -8,6 +10,7 @@ use std::path::PathBuf; use codex_protocol::ThreadId; use serde::Deserialize; use serde::Serialize; +use tokio::io::AsyncBufReadExt; use tokio::io::AsyncWriteExt; const SESSION_INDEX_FILE: &str = "session_index.jsonl"; @@ -76,6 +79,38 @@ pub async fn find_thread_name_by_id( Ok(entry.map(|entry| entry.thread_name)) } +/// Find the latest thread names for a batch of thread ids. +pub async fn find_thread_names_by_ids( + codex_home: &Path, + thread_ids: &HashSet, +) -> std::io::Result> { + let path = session_index_path(codex_home); + if thread_ids.is_empty() || !path.exists() { + return Ok(HashMap::new()); + } + + let file = tokio::fs::File::open(&path).await?; + let reader = tokio::io::BufReader::new(file); + let mut lines = reader.lines(); + let mut names = HashMap::with_capacity(thread_ids.len()); + + while let Some(line) = lines.next_line().await? { + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + let Ok(entry) = serde_json::from_str::(trimmed) else { + continue; + }; + let name = entry.thread_name.trim(); + if !name.is_empty() && thread_ids.contains(&entry.id) { + names.insert(entry.id, name.to_string()); + } + } + + Ok(names) +} + /// Find the most recently updated thread id for a thread name, if any. pub async fn find_thread_id_by_name( codex_home: &Path, @@ -197,6 +232,8 @@ where mod tests { use super::*; use pretty_assertions::assert_eq; + use std::collections::HashMap; + use std::collections::HashSet; use tempfile::TempDir; fn write_index(path: &Path, lines: &[SessionIndexEntry]) -> std::io::Result<()> { let mut out = String::new(); @@ -279,6 +316,44 @@ mod tests { Ok(()) } + #[tokio::test] + async fn find_thread_names_by_ids_prefers_latest_entry() -> std::io::Result<()> { + let temp = TempDir::new()?; + let path = session_index_path(temp.path()); + let id1 = ThreadId::new(); + let id2 = ThreadId::new(); + let lines = vec![ + SessionIndexEntry { + id: id1, + thread_name: "first".to_string(), + updated_at: "2024-01-01T00:00:00Z".to_string(), + }, + SessionIndexEntry { + id: id2, + thread_name: "other".to_string(), + updated_at: "2024-01-01T00:00:00Z".to_string(), + }, + SessionIndexEntry { + id: id1, + thread_name: "latest".to_string(), + updated_at: "2024-01-02T00:00:00Z".to_string(), + }, + ]; + write_index(&path, &lines)?; + + let mut ids = HashSet::new(); + ids.insert(id1); + ids.insert(id2); + + let mut expected = HashMap::new(); + expected.insert(id1, "latest".to_string()); + expected.insert(id2, "other".to_string()); + + let found = find_thread_names_by_ids(temp.path(), &ids).await?; + assert_eq!(found, expected); + Ok(()) + } + #[test] fn scan_index_finds_latest_match_among_mixed_entries() -> std::io::Result<()> { let temp = TempDir::new()?; diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index 4ce17721cd..e80c4ad793 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; @@ -11,6 +12,7 @@ use codex_core::RolloutRecorder; use codex_core::ThreadItem; use codex_core::ThreadSortKey; use codex_core::ThreadsPage; +use codex_core::find_thread_names_by_ids; use codex_core::path_utils; use codex_protocol::items::TurnItem; use color_eyre::eyre::Result; @@ -34,12 +36,12 @@ use crate::text_formatting::truncate_text; use crate::tui::FrameRequester; use crate::tui::Tui; use crate::tui::TuiEvent; +use codex_protocol::ThreadId; use codex_protocol::models::ResponseItem; use codex_protocol::protocol::SessionMetaLine; const PAGE_SIZE: usize = 25; const LOAD_NEAR_THRESHOLD: usize = 5; - #[derive(Debug, Clone)] pub enum SessionSelection { StartFresh, @@ -97,8 +99,9 @@ enum BackgroundEvent { } /// Interactive session picker that lists recorded rollout files with simple -/// search and pagination. Shows the first user input as the preview, relative -/// time (e.g., "5 seconds ago"), and the absolute path. +/// search and pagination. Shows the session name when available, otherwise the +/// first user input as the preview, relative time (e.g., "5 seconds ago"), and +/// the absolute path. pub async fn run_resume_picker( tui: &mut Tui, codex_home: &Path, @@ -210,7 +213,7 @@ async fn run_session_picker( } } Some(event) = background_events.next() => { - state.handle_background_event(event)?; + state.handle_background_event(event).await?; } else => break, } @@ -257,6 +260,7 @@ struct PickerState { show_all: bool, filter_cwd: Option, action: SessionPickerAction, + thread_name_cache: HashMap>, } struct PaginationState { @@ -312,12 +316,32 @@ impl SearchState { struct Row { path: PathBuf, preview: String, + thread_id: Option, + thread_name: Option, created_at: Option>, updated_at: Option>, cwd: Option, git_branch: Option, } +impl Row { + fn display_preview(&self) -> &str { + self.thread_name.as_deref().unwrap_or(&self.preview) + } + + fn matches_query(&self, query: &str) -> bool { + if self.preview.to_lowercase().contains(query) { + return true; + } + if let Some(thread_name) = self.thread_name.as_ref() + && thread_name.to_lowercase().contains(query) + { + return true; + } + false + } +} + impl PickerState { fn new( codex_home: PathBuf, @@ -352,6 +376,7 @@ impl PickerState { show_all, filter_cwd, action, + thread_name_cache: HashMap::new(), } } @@ -453,7 +478,7 @@ impl PickerState { }); } - fn handle_background_event(&mut self, event: BackgroundEvent) -> Result<()> { + async fn handle_background_event(&mut self, event: BackgroundEvent) -> Result<()> { match event { BackgroundEvent::PageLoaded { request_token, @@ -470,6 +495,7 @@ impl PickerState { self.pagination.loading = LoadingState::Idle; let page = page.map_err(color_eyre::Report::from)?; self.ingest_page(page); + self.update_thread_names().await; let completed_token = pending.search_token.or(search_token); self.continue_search_if_token_matches(completed_token); } @@ -508,6 +534,48 @@ impl PickerState { self.apply_filter(); } + async fn update_thread_names(&mut self) { + let mut missing_ids = HashSet::new(); + for row in &self.all_rows { + let Some(thread_id) = row.thread_id else { + continue; + }; + if self.thread_name_cache.contains_key(&thread_id) { + continue; + } + missing_ids.insert(thread_id); + } + + if missing_ids.is_empty() { + return; + } + + let names = find_thread_names_by_ids(&self.codex_home, &missing_ids) + .await + .unwrap_or_default(); + for thread_id in missing_ids { + let thread_name = names.get(&thread_id).cloned(); + self.thread_name_cache.insert(thread_id, thread_name); + } + + let mut updated = false; + for row in self.all_rows.iter_mut() { + let Some(thread_id) = row.thread_id else { + continue; + }; + let thread_name = self.thread_name_cache.get(&thread_id).cloned().flatten(); + if row.thread_name == thread_name { + continue; + } + row.thread_name = thread_name; + updated = true; + } + + if updated { + self.apply_filter(); + } + } + fn apply_filter(&mut self) { let base_iter = self .all_rows @@ -517,10 +585,7 @@ impl PickerState { self.filtered_rows = base_iter.cloned().collect(); } else { let q = self.query.to_lowercase(); - self.filtered_rows = base_iter - .filter(|r| r.preview.to_lowercase().contains(&q)) - .cloned() - .collect(); + self.filtered_rows = base_iter.filter(|r| r.matches_query(&q)).cloned().collect(); } if self.selected >= self.filtered_rows.len() { self.selected = self.filtered_rows.len().saturating_sub(1); @@ -712,7 +777,7 @@ fn head_to_row(item: &ThreadItem) -> Row { .and_then(parse_timestamp_str) .or(created_at); - let (cwd, git_branch) = extract_session_meta_from_head(&item.head); + let (cwd, git_branch, thread_id) = extract_session_meta_from_head(&item.head); let preview = preview_from_head(&item.head) .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) @@ -721,6 +786,8 @@ fn head_to_row(item: &ThreadItem) -> Row { Row { path: item.path.clone(), preview, + thread_id, + thread_name: None, created_at, updated_at, cwd, @@ -728,15 +795,18 @@ fn head_to_row(item: &ThreadItem) -> Row { } } -fn extract_session_meta_from_head(head: &[serde_json::Value]) -> (Option, Option) { +fn extract_session_meta_from_head( + head: &[serde_json::Value], +) -> (Option, Option, Option) { for value in head { if let Ok(meta_line) = serde_json::from_value::(value.clone()) { let cwd = Some(meta_line.meta.cwd); let git_branch = meta_line.git.and_then(|git| git.branch); - return (cwd, git_branch); + let thread_id = Some(meta_line.meta.id); + return (cwd, git_branch, thread_id); } } - (None, None) + (None, None, None) } fn paths_match(a: &Path, b: &Path) -> bool { @@ -909,7 +979,7 @@ fn render_list( if add_leading_gap { preview_width = preview_width.saturating_sub(2); } - let preview = truncate_text(&row.preview, preview_width); + let preview = truncate_text(row.display_preview(), preview_width); let mut spans: Vec = vec![marker]; if let Some(updated) = updated_span { spans.push(updated); @@ -1252,6 +1322,22 @@ mod tests { assert_eq!(row.updated_at, Some(expected_updated)); } + #[test] + fn row_display_preview_prefers_thread_name() { + let row = Row { + path: PathBuf::from("/tmp/a.jsonl"), + preview: String::from("first message"), + thread_id: None, + thread_name: Some(String::from("My session")), + created_at: None, + updated_at: None, + cwd: None, + git_branch: None, + }; + + assert_eq!(row.display_preview(), "My session"); + } + #[test] fn resume_table_snapshot() { use crate::custom_terminal::Terminal; @@ -1275,6 +1361,8 @@ mod tests { Row { path: PathBuf::from("/tmp/a.jsonl"), preview: String::from("Fix resume picker timestamps"), + thread_id: None, + thread_name: None, created_at: Some(now - Duration::minutes(16)), updated_at: Some(now - Duration::seconds(42)), cwd: None, @@ -1283,6 +1371,8 @@ mod tests { Row { path: PathBuf::from("/tmp/b.jsonl"), preview: String::from("Investigate lazy pagination cap"), + thread_id: None, + thread_name: None, created_at: Some(now - Duration::hours(1)), updated_at: Some(now - Duration::minutes(35)), cwd: None, @@ -1291,6 +1381,8 @@ mod tests { Row { path: PathBuf::from("/tmp/c.jsonl"), preview: String::from("Explain the codebase"), + thread_id: None, + thread_name: None, created_at: Some(now - Duration::hours(2)), updated_at: Some(now - Duration::hours(2)), cwd: None, @@ -1488,6 +1580,104 @@ mod tests { assert_snapshot!("resume_picker_screen", snapshot); } + #[tokio::test] + async fn resume_picker_thread_names_snapshot() { + use crate::custom_terminal::Terminal; + use crate::test_backend::VT100Backend; + use ratatui::layout::Constraint; + use ratatui::layout::Layout; + + let tempdir = tempfile::tempdir().expect("tempdir"); + let session_index_path = tempdir.path().join("session_index.jsonl"); + + let id1 = + ThreadId::from_string("11111111-1111-1111-1111-111111111111").expect("thread id 1"); + let id2 = + ThreadId::from_string("22222222-2222-2222-2222-222222222222").expect("thread id 2"); + let entries = vec![ + json!({ + "id": id1, + "thread_name": "Keep this for now", + "updated_at": "2025-01-01T00:00:00Z", + }), + json!({ + "id": id2, + "thread_name": "Named thread", + "updated_at": "2025-01-01T00:00:00Z", + }), + ]; + let mut out = String::new(); + for entry in entries { + out.push_str(&serde_json::to_string(&entry).expect("session index entry")); + out.push('\n'); + } + std::fs::write(&session_index_path, out).expect("write session index"); + + let loader: PageLoader = Arc::new(|_| {}); + let mut state = PickerState::new( + tempdir.path().to_path_buf(), + FrameRequester::test_dummy(), + loader, + String::from("openai"), + true, + None, + SessionPickerAction::Resume, + ); + + let now = Utc::now(); + let rows = vec![ + Row { + path: PathBuf::from("/tmp/a.jsonl"), + preview: String::from("First message preview"), + thread_id: Some(id1), + thread_name: None, + created_at: None, + updated_at: Some(now - Duration::days(2)), + cwd: None, + git_branch: None, + }, + Row { + path: PathBuf::from("/tmp/b.jsonl"), + preview: String::from("Second message preview"), + thread_id: Some(id2), + thread_name: None, + created_at: None, + updated_at: Some(now - Duration::days(3)), + cwd: None, + git_branch: None, + }, + ]; + state.all_rows = rows.clone(); + state.filtered_rows = rows; + state.view_rows = Some(2); + state.selected = 0; + state.scroll_top = 0; + state.update_view_rows(2); + + state.update_thread_names().await; + + let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all); + + let width: u16 = 80; + let height: u16 = 5; + let backend = VT100Backend::new(width, height); + let mut terminal = Terminal::with_options(backend).expect("terminal"); + terminal.set_viewport_area(Rect::new(0, 0, width, height)); + + { + let mut frame = terminal.get_frame(); + let area = frame.area(); + let segments = + Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(area); + render_column_headers(&mut frame, segments[0], &metrics); + render_list(&mut frame, segments[1], &state, &metrics); + } + terminal.flush().expect("flush"); + + let snapshot = terminal.backend().to_string(); + assert_snapshot!("resume_picker_thread_names", snapshot); + } + #[test] fn pageless_scrolling_deduplicates_and_keeps_order() { let loader: PageLoader = Arc::new(|_| {}); @@ -1674,8 +1864,8 @@ mod tests { assert_eq!(state.selected, state.filtered_rows.len().saturating_sub(2)); } - #[test] - fn set_query_loads_until_match_and_respects_scan_cap() { + #[tokio::test] + async fn set_query_loads_until_match_and_respects_scan_cap() { let recorded_requests: Arc>> = Arc::new(Mutex::new(Vec::new())); let request_sink = recorded_requests.clone(); let loader: PageLoader = Arc::new(move |req: PageLoadRequest| { @@ -1726,6 +1916,7 @@ mod tests { false, )), }) + .await .unwrap(); let second_request = { @@ -1753,6 +1944,7 @@ mod tests { false, )), }) + .await .unwrap(); assert!(!state.filtered_rows.is_empty()); @@ -1772,6 +1964,7 @@ mod tests { search_token: second_request.search_token, page: Ok(page(Vec::new(), None, 0, false)), }) + .await .unwrap(); assert_eq!(recorded_requests.lock().unwrap().len(), 1); @@ -1781,6 +1974,7 @@ mod tests { search_token: active_request.search_token, page: Ok(page(Vec::new(), None, 3, true)), }) + .await .unwrap(); assert!(state.filtered_rows.is_empty()); diff --git a/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap new file mode 100644 index 0000000000..e9bca47d40 --- /dev/null +++ b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap @@ -0,0 +1,8 @@ +--- +source: tui/src/resume_picker.rs +assertion_line: 1683 +expression: snapshot +--- + Updated Branch CWD Conversation +> 2 days ago - - Keep this for now + 3 days ago - - Named thread From 9513f18bfe9edd9795f86c9645bdd023189a567c Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 2 Feb 2026 11:57:44 +0100 Subject: [PATCH 064/139] chore: collab experimental (#10381) --- codex-rs/core/src/features.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 97fde03382..954b3465b6 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -520,7 +520,11 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::Collab, key: "collab", - stage: Stage::UnderDevelopment, + stage: Stage::Experimental { + name: "Sub-agents", + menu_description: "Ask Codex to spawn multiple agents to parallelize the work and win in efficiency.", + announcement: "NEW: Sub-agents can now be spawned by Codex. Enable in /experimental and restart Codex!", + }, default_enabled: false, }, FeatureSpec { From 3cc9122ee2596d937def5ed6fcd221dc446813b5 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 2 Feb 2026 12:06:50 +0100 Subject: [PATCH 065/139] feat: experimental flags (#10231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem being solved - We need a single, reliable way to mark app-server API surface as experimental so that: 1. the runtime can reject experimental usage unless the client opts in 2. generated TS/JSON schemas can exclude experimental methods/fields for stable clients. Right now that’s easy to drift or miss when done ad-hoc. ## How to declare experimental methods and fields - **Experimental method**: add `#[experimental("method/name")]` to the `ClientRequest` variant in `client_request_definitions!`. - **Experimental field**: on the params struct, derive `ExperimentalApi` and annotate the field with `#[experimental("method/name.field")]` + set `inspect_params: true` for the method variant so `ClientRequest::experimental_reason()` inspects params for experimental fields. ## How the macro solves it - The new derive macro lives in `codex-rs/codex-experimental-api-macros/src/lib.rs` and is used via `#[derive(ExperimentalApi)]` plus `#[experimental("reason")]` attributes. - **Structs**: - Generates `ExperimentalApi::experimental_reason(&self)` that checks only annotated fields. - The “presence” check is type-aware: - `Option`: `is_some_and(...)` recursively checks inner. - `Vec`/`HashMap`/`BTreeMap`: must be non-empty. - `bool`: must be `true`. - Other types: considered present (returns `true`). - Registers each experimental field in an `inventory` with `(type_name, serialized field name, reason)` and exposes `EXPERIMENTAL_FIELDS` for that type. Field names are converted from `snake_case` to `camelCase` for schema/TS filtering. - **Enums**: - Generates an exhaustive `match` returning `Some(reason)` for annotated variants and `None` otherwise (no wildcard arm). - **Wiring**: - Runtime gating uses `ExperimentalApi::experimental_reason()` in `codex-rs/app-server/src/message_processor.rs` to reject requests unless `InitializeParams.capabilities.experimental_api == true`. - Schema/TS export filters use the inventory list and `EXPERIMENTAL_CLIENT_METHODS` from `client_request_definitions!` to strip experimental methods/fields when `experimental_api` is false. --- codex-rs/Cargo.lock | 1748 +++++++++-------- codex-rs/Cargo.toml | 3 + codex-rs/app-server-protocol/Cargo.toml | 2 + .../schema/json/ClientRequest.json | 59 +- .../codex_app_server_protocol.schemas.json | 78 +- .../schema/json/v1/InitializeParams.json | 21 + .../json/v2/CollaborationModeListParams.json | 6 - .../v2/CollaborationModeListResponse.json | 93 - .../schema/json/v2/ThreadStartParams.json | 9 - .../schema/typescript/ClientRequest.ts | 3 +- .../typescript/InitializeCapabilities.ts | 12 + .../schema/typescript/InitializeParams.ts | 3 +- .../schema/typescript/index.ts | 1 + .../v2/CollaborationModeListParams.ts | 8 - .../v2/CollaborationModeListResponse.ts | 9 - .../schema/typescript/v2/ThreadStartParams.ts | 6 +- .../schema/typescript/v2/index.ts | 2 - .../src/experimental_api.rs | 70 + codex-rs/app-server-protocol/src/export.rs | 856 +++++++- codex-rs/app-server-protocol/src/lib.rs | 5 + .../src/protocol/common.rs | 108 +- .../app-server-protocol/src/protocol/v1.rs | 11 + .../app-server-protocol/src/protocol/v2.rs | 26 +- codex-rs/app-server-test-client/src/main.rs | 4 + codex-rs/app-server/README.md | 27 + .../app-server/src/codex_message_processor.rs | 16 + codex-rs/app-server/src/message_processor.rs | 25 + .../app-server/tests/common/mcp_process.rs | 38 +- .../tests/suite/v2/experimental_api.rs | 160 ++ codex-rs/app-server/tests/suite/v2/mod.rs | 1 + codex-rs/cli/src/main.rs | 20 +- .../codex-experimental-api-macros/BUILD.bazel | 7 + .../codex-experimental-api-macros/Cargo.toml | 16 + .../codex-experimental-api-macros/src/lib.rs | 293 +++ codex-rs/debug-client/src/client.rs | 4 + codex-rs/utils/cargo-bin/src/lib.rs | 1 + defs.bzl | 7 +- 37 files changed, 2682 insertions(+), 1076 deletions(-) delete mode 100644 codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json delete mode 100644 codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/InitializeCapabilities.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts create mode 100644 codex-rs/app-server-protocol/src/experimental_api.rs create mode 100644 codex-rs/app-server/tests/suite/v2/experimental_api.rs create mode 100644 codex-rs/codex-experimental-api-macros/BUILD.bazel create mode 100644 codex-rs/codex-experimental-api-macros/Cargo.toml create mode 100644 codex-rs/codex-experimental-api-macros/src/lib.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 774cb9615f..4614e5f24f 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -154,7 +154,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.1", + "socket2 0.6.2", "time", "tracing", "url", @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -193,7 +193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -229,7 +229,7 @@ checksum = "fe233a377643e0fc1a56421d7c90acdec45c291b30345eb9f08e8d0ddce5a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -301,22 +301,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -408,13 +408,12 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.17" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" dependencies = [ "anstyle", "bstr", - "doc-comment", "libc", "predicates", "predicates-core", @@ -490,16 +489,16 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.0.8", + "rustix 1.1.3", "slab", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -521,7 +520,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.0.8", + "rustix 1.1.3", ] [[package]] @@ -532,7 +531,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -547,10 +546,10 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.8", + "rustix 1.1.3", "signal-hook-registry", "slab", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -572,7 +571,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -589,7 +588,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -632,7 +631,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -661,7 +660,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body", "http-body-util", "mime", @@ -673,9 +672,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -683,7 +682,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -694,9 +693,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "beef" @@ -719,7 +718,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -770,6 +769,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "blocking" version = "1.6.2" @@ -794,9 +802,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -805,15 +813,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -829,9 +837,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "bytestring" @@ -868,9 +876,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.52" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -895,9 +903,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -933,7 +941,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -965,9 +973,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ "clap_builder", "clap_derive", @@ -975,9 +983,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ "anstream", "anstyle", @@ -988,30 +996,30 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.64" +version = "4.5.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0da80818b2d95eca9aa614a30783e42f62bf5fdfee24e68cfb960b071ba8d1" +checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "clipboard-win" @@ -1058,13 +1066,13 @@ dependencies = [ "codex-protocol", "eventsource-stream", "futures", - "http 1.3.1", + "http 1.4.0", "pretty_assertions", "regex-lite", "reqwest", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-test", "tokio-tungstenite", @@ -1111,7 +1119,7 @@ dependencies = [ "tempfile", "time", "tokio", - "toml 0.9.5", + "toml 0.9.11+spec-1.1.0", "tracing", "tracing-subscriber", "uuid", @@ -1124,9 +1132,11 @@ version = "0.0.0" dependencies = [ "anyhow", "clap", + "codex-experimental-api-macros", "codex-protocol", "codex-utils-absolute-path", "codex-utils-cargo-bin", + "inventory", "mcp-types", "pretty_assertions", "schemars 0.8.22", @@ -1135,7 +1145,7 @@ dependencies = [ "similar", "strum_macros 0.27.2", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "ts-rs", "uuid", ] @@ -1164,7 +1174,7 @@ dependencies = [ "pretty_assertions", "similar", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tree-sitter", "tree-sitter-bash", ] @@ -1269,7 +1279,7 @@ dependencies = [ "supports-color 3.0.2", "tempfile", "tokio", - "toml 0.9.5", + "toml 0.9.11+spec-1.1.0", "tracing", ] @@ -1281,14 +1291,14 @@ dependencies = [ "bytes", "eventsource-stream", "futures", - "http 1.3.1", + "http 1.4.0", "opentelemetry", "opentelemetry_sdk", "rand 0.9.2", "reqwest", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "tracing-opentelemetry", @@ -1310,7 +1320,7 @@ dependencies = [ "serde_json", "tempfile", "tokio", - "toml 0.9.5", + "toml 0.9.11+spec-1.1.0", "tracing", ] @@ -1355,7 +1365,7 @@ dependencies = [ "diffy", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1370,7 +1380,7 @@ dependencies = [ "codex-utils-absolute-path", "pretty_assertions", "serde", - "toml 0.9.5", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -1417,10 +1427,10 @@ dependencies = [ "env-flags", "eventsource-stream", "futures", - "http 1.3.1", + "http 1.4.0", "image", "include_dir", - "indexmap 2.12.0", + "indexmap 2.13.0", "indoc", "keyring", "landlock", @@ -1452,12 +1462,12 @@ dependencies = [ "tempfile", "test-case", "test-log", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "tokio-tungstenite", "tokio-util", - "toml 0.9.5", + "toml 0.9.11+spec-1.1.0", "toml_edit 0.24.0+spec-1.1.0", "tracing", "tracing-subscriber", @@ -1538,7 +1548,7 @@ dependencies = [ "serde", "serde_json", "shlex", - "socket2 0.6.1", + "socket2 0.6.2", "tempfile", "tokio", "tokio-util", @@ -1559,7 +1569,7 @@ dependencies = [ "shlex", "starlark", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1582,6 +1592,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "codex-experimental-api-macros" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "codex-feedback" version = "0.0.0" @@ -1621,7 +1640,7 @@ dependencies = [ "schemars 0.8.22", "serde", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "ts-rs", "walkdir", ] @@ -1770,7 +1789,7 @@ dependencies = [ "codex-protocol", "codex-utils-absolute-path", "eventsource-stream", - "http 1.3.1", + "http 1.4.0", "opentelemetry", "opentelemetry-appender-tracing", "opentelemetry-otlp", @@ -1781,7 +1800,7 @@ dependencies = [ "serde", "serde_json", "strum_macros 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-tungstenite", "tracing", @@ -1963,11 +1982,11 @@ dependencies = [ "supports-color 3.0.2", "tempfile", "textwrap 0.16.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", - "toml 0.9.5", + "toml 0.9.11+spec-1.1.0", "tracing", "tracing-appender", "tracing-subscriber", @@ -2012,7 +2031,7 @@ version = "0.0.0" dependencies = [ "assert_cmd", "runfiles", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2032,7 +2051,7 @@ dependencies = [ "codex-utils-cache", "image", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", ] @@ -2042,7 +2061,7 @@ version = "0.0.0" dependencies = [ "pretty_assertions", "serde_json", - "toml 0.9.5", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -2067,7 +2086,7 @@ version = "0.0.0" dependencies = [ "assert_matches", "async-trait", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", ] @@ -2399,9 +2418,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -2454,16 +2473,6 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - [[package]] name = "darling" version = "0.21.3" @@ -2484,20 +2493,6 @@ dependencies = [ "darling_macro 0.23.0", ] -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn 2.0.104", -] - [[package]] name = "darling_core" version = "0.21.3" @@ -2509,7 +2504,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2522,18 +2517,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.104", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2544,7 +2528,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2555,7 +2539,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2566,9 +2550,9 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "dbus" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9" +checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" dependencies = [ "libc", "libdbus-sys", @@ -2645,9 +2629,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -2691,7 +2675,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "unicode-xid", ] @@ -2705,7 +2689,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.104", + "syn 2.0.114", "unicode-xid", ] @@ -2769,8 +2753,8 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", - "windows-sys 0.61.1", + "redox_users 0.5.2", + "windows-sys 0.61.2", ] [[package]] @@ -2812,15 +2796,9 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "dotenvy" version = "0.15.7" @@ -2835,9 +2813,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dtor" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" dependencies = [ "dtor-proc-macro", ] @@ -2871,14 +2849,14 @@ checksum = "83e195b4945e88836d826124af44fdcb262ec01ef94d44f14f4fb5103f19892a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" @@ -2915,9 +2893,9 @@ dependencies = [ [[package]] name = "endi" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] name = "endian-type" @@ -2940,7 +2918,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2961,7 +2939,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2972,9 +2950,9 @@ checksum = "dbfd0e7fc632dec5e6c9396a27bc9f9975b4e039720e1fd3e34021d3ce28c415" [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -3016,12 +2994,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3043,9 +3021,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -3101,7 +3079,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -3121,7 +3099,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -3131,7 +3109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.0.8", + "rustix 1.1.3", "windows-sys 0.59.0", ] @@ -3157,9 +3135,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "findshlibs" @@ -3175,9 +3153,9 @@ dependencies = [ [[package]] name = "fixed_decimal" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35943d22b2f19c0cb198ecf915910a8158e94541c89dcc63300d7799d46c2c5e" +checksum = "35eabf480f94d69182677e37571d3be065822acfafd12f2f085db44fbbcc8e57" dependencies = [ "displaydoc", "smallvec", @@ -3191,10 +3169,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] -name = "flate2" -version = "1.1.2" +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -3277,7 +3261,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -3294,9 +3278,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -3406,7 +3390,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -3459,8 +3443,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.2.0", - "windows-result 0.3.4", + "windows-link", + "windows-result 0.4.1", ] [[package]] @@ -3475,55 +3459,55 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.4.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "libc", - "windows-targets 0.48.5", + "rustix 1.1.3", + "windows-link", ] [[package]] name = "getopts" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ "unicode-width 0.2.1", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" @@ -3541,22 +3525,22 @@ dependencies = [ "bstr", "log", "regex-automata", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", ] [[package]] name = "h2" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.3.1", - "indexmap 2.12.0", + "http 1.4.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3565,12 +3549,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -3591,9 +3576,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -3602,9 +3587,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", @@ -3617,7 +3602,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -3656,7 +3641,7 @@ dependencies = [ "once_cell", "rand 0.9.2", "ring", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tokio", "tracing", @@ -3679,7 +3664,7 @@ dependencies = [ "rand 0.9.2", "resolv-conf", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -3704,22 +3689,22 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "hostname" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" dependencies = [ "cfg-if", "libc", - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -3735,12 +3720,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -3751,7 +3735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -3762,7 +3746,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body", "pin-project-lite", ] @@ -3787,16 +3771,16 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", - "http 1.3.1", + "http 1.4.0", "http-body", "httparse", "httpdate", @@ -3814,7 +3798,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.3.1", + "http 1.4.0", "hyper", "hyper-util", "rustls", @@ -3823,7 +3807,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -3857,23 +3841,23 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -3883,9 +3867,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3893,7 +3877,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -3970,9 +3954,9 @@ dependencies = [ [[package]] name = "icu_locale_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03e2fcaefecdf05619f3d6f91740e79ab969b4dd54f77cbf546b1d0d28e3147" +checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831" [[package]] name = "icu_normalizer" @@ -3996,9 +3980,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -4010,9 +3994,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -4039,9 +4023,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -4060,9 +4044,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" dependencies = [ "crossbeam-deque", "globset", @@ -4086,8 +4070,8 @@ dependencies = [ "num-traits", "png", "tiff", - "zune-core 0.5.0", - "zune-jpeg 0.5.5", + "zune-core 0.5.1", + "zune-jpeg 0.5.12", ] [[package]] @@ -4117,9 +4101,9 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" @@ -4134,21 +4118,24 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "inotify" @@ -4182,9 +4169,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.46.0" +version = "1.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5" +checksum = "38c91d64f9ad425e80200a50a0e8b8a641680b44e33ce832efe5b8bc65161b07" dependencies = [ "console", "once_cell", @@ -4194,22 +4181,22 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "indoc", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "inventory" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" dependencies = [ "rustversion", ] @@ -4234,9 +4221,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -4244,13 +4231,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4261,9 +4248,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -4294,32 +4281,32 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -4350,15 +4337,15 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -4415,7 +4402,7 @@ dependencies = [ "is-terminal", "itertools 0.10.5", "lalrpop-util", - "petgraph", + "petgraph 0.6.5", "regex", "regex-syntax 0.6.29", "string_cache", @@ -4441,7 +4428,7 @@ checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4461,15 +4448,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libdbus-sys" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" dependencies = [ "pkg-config", ] @@ -4481,7 +4468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -4492,13 +4479,13 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall", + "redox_syscall 0.7.0", ] [[package]] @@ -4530,15 +4517,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "local-waker" @@ -4548,11 +4535,10 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -4607,7 +4593,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -4616,7 +4602,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -4712,9 +4698,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -4768,21 +4754,21 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "moka" -version = "0.12.12" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -4797,9 +4783,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.5" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" dependencies = [ "num-traits", "pxfm", @@ -4823,7 +4809,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -4934,17 +4920,20 @@ dependencies = [ [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.10.0", +] [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5017,9 +5006,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -5089,8 +5078,8 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64", "chrono", - "getrandom 0.2.16", - "http 1.3.1", + "getrandom 0.2.17", + "http 1.4.0", "rand 0.8.5", "reqwest", "serde", @@ -5103,18 +5092,18 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.10.0", "objc2", @@ -5123,10 +5112,31 @@ dependencies = [ ] [[package]] -name = "objc2-core-foundation" -version = "0.3.1" +name = "objc2-cloud-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.10.0", "dispatch2", @@ -5135,9 +5145,9 @@ dependencies = [ [[package]] name = "objc2-core-graphics" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.10.0", "dispatch2", @@ -5146,6 +5156,38 @@ dependencies = [ "objc2-io-surface", ] +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -5154,20 +5196,22 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.10.0", + "block2", + "libc", "objc2", "objc2-core-foundation", ] [[package]] name = "objc2-io-surface" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.10.0", "objc2", @@ -5175,10 +5219,53 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.7" +name = "objc2-quartz-core" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -5195,15 +5282,15 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -5222,7 +5309,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -5232,10 +5319,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] -name = "openssl-src" -version = "300.5.1+3.5.1" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" dependencies = [ "cc", ] @@ -5263,7 +5356,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -5287,7 +5380,7 @@ checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" dependencies = [ "async-trait", "bytes", - "http 1.3.1", + "http 1.4.0", "opentelemetry", "reqwest", ] @@ -5298,7 +5391,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" dependencies = [ - "http 1.3.1", + "http 1.4.0", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -5306,7 +5399,7 @@ dependencies = [ "prost", "reqwest", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tonic", "tracing", @@ -5347,7 +5440,7 @@ dependencies = [ "opentelemetry", "percent-encoding", "rand 0.9.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", ] @@ -5370,31 +5463,35 @@ dependencies = [ [[package]] name = "os_info" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" dependencies = [ + "android_system_properties", "log", - "plist", + "nix 0.30.1", + "objc2", + "objc2-foundation", + "objc2-ui-kit", "serde", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "os_pipe" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "owo-colors" -version = "4.2.2" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" dependencies = [ "supports-color 2.1.0", "supports-color 3.0.2", @@ -5408,9 +5505,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -5418,15 +5515,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -5437,9 +5534,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pastey" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d6c094ee800037dff99e02cab0eaf3142826586742a270ab3d7a62656bd27a" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] name = "path-absolutize" @@ -5476,9 +5573,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" @@ -5486,8 +5583,19 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", - "indexmap 2.12.0", + "fixedbitset 0.4.2", + "indexmap 2.13.0", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.5", + "indexmap 2.13.0", ] [[package]] @@ -5516,7 +5624,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -5569,19 +5677,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plist" -version = "1.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" -dependencies = [ - "base64", - "indexmap 2.12.0", - "quick-xml 0.38.0", - "serde", - "time", -] - [[package]] name = "png" version = "0.18.0" @@ -5605,21 +5700,21 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.8", - "windows-sys 0.61.1", + "rustix 1.1.3", + "windows-sys 0.61.2", ] [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -5728,25 +5823,25 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "process-wrap" -version = "9.0.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5fd83ab7fa55fd06f5e665e3fc52b8bca451c0486b8ea60ad649cd1c10a5da" +checksum = "fd1395947e69c07400ef4d43db0051d6f773c21f647ad8b97382fc01f0204c60" dependencies = [ "futures", - "indexmap 2.12.0", + "indexmap 2.13.0", "nix 0.30.1", "tokio", "tracing", - "windows 0.61.3", + "windows 0.62.2", ] [[package]] @@ -5760,15 +5855,15 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", "unarray", ] [[package]] name = "prost" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", @@ -5776,22 +5871,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "psl" -version = "2.1.178" +version = "2.1.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a54161bdf033552d905f5e653fb6263690e63df54745ad5199a5f8fa802a0d6" +checksum = "81dc6a90669f481b41cae3005c68efa36bef275b95aa9123a7af7f1c68c6e5b2" dependencies = [ "psl-types", ] @@ -5823,9 +5918,9 @@ checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" [[package]] name = "pxfm" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" dependencies = [ "num-traits", ] @@ -5838,18 +5933,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] @@ -5867,8 +5953,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", - "thiserror 2.0.17", + "socket2 0.6.2", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -5881,7 +5967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", @@ -5889,7 +5975,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -5904,16 +5990,16 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -5946,9 +6032,9 @@ dependencies = [ [[package]] name = "rama-boring" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "288926585d0b8ed1b1dd278a31ea1367007ad0bd4263ca84810e10939c2398a3" +checksum = "84f7f862c81618f9aef40bd32e73986321109a24272c79e040377c5ac29491e8" dependencies = [ "bitflags 2.10.0", "foreign-types 0.5.0", @@ -5959,9 +6045,9 @@ dependencies = [ [[package]] name = "rama-boring-sys" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421ebb40444a6d740f867a5055a710a73e7cd74b9b389583b45e9b29d1330465" +checksum = "d5bfe3e86d71e9b91dae7561d5ceeaceb37a7d4fc078ab241afd7aab777f606f" dependencies = [ "bindgen", "cmake", @@ -5971,9 +6057,9 @@ dependencies = [ [[package]] name = "rama-boring-tokio" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913cf3d377b37ff903cd57c2f6133ba7da5b7e0fe94821dec83daa8a024bb6ce" +checksum = "9c7d71fab2ce4408cc40f819865501dbc63272ddab0e77dd3500ff77f1a0f883" dependencies = [ "rama-boring", "rama-boring-sys", @@ -6036,7 +6122,7 @@ dependencies = [ "chrono", "const_format", "csv", - "http 1.3.1", + "http 1.4.0", "http-range-header", "httpdate", "iri-string", @@ -6090,7 +6176,7 @@ dependencies = [ "futures-channel", "httparse", "httpdate", - "indexmap 2.12.0", + "indexmap 2.13.0", "itoa", "parking_lot", "pin-project-lite", @@ -6136,7 +6222,7 @@ dependencies = [ "bytes", "const_format", "fnv", - "http 1.3.1", + "http 1.4.0", "http-body", "http-body-util", "itoa", @@ -6165,7 +6251,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -6192,7 +6278,7 @@ dependencies = [ "rama-utils", "serde", "sha2", - "socket2 0.6.1", + "socket2 0.6.2", "tokio", ] @@ -6309,7 +6395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -6329,7 +6415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -6338,16 +6424,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -6356,7 +6442,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -6410,9 +6496,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -6423,40 +6518,40 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 1.0.69", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -6468,7 +6563,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", ] [[package]] @@ -6479,7 +6574,7 @@ checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", ] [[package]] @@ -6496,15 +6591,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -6513,7 +6608,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 1.3.1", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -6546,7 +6641,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -6563,7 +6658,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -6580,7 +6675,7 @@ dependencies = [ "bytes", "chrono", "futures", - "http 1.3.1", + "http 1.4.0", "http-body", "http-body-util", "oauth2", @@ -6590,11 +6685,11 @@ dependencies = [ "rand 0.9.2", "reqwest", "rmcp-macros", - "schemars 1.0.4", + "schemars 1.2.1", "serde", "serde_json", "sse-stream", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", @@ -6614,7 +6709,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -6644,9 +6739,9 @@ source = "git+https://github.com/dzbarsky/rules_rust?rev=b56cbaa8465e74127f1ea21 [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -6678,22 +6773,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", @@ -6706,11 +6801,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -6718,9 +6813,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -6728,9 +6823,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -6739,9 +6834,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustyline" @@ -6767,9 +6862,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -6795,7 +6890,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -6866,14 +6961,14 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "chrono", "dyn-clone", "ref-cast", - "schemars_derive 1.0.4", + "schemars_derive 1.2.1", "serde", "serde_json", ] @@ -6887,19 +6982,19 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "schemars_derive" -version = "1.0.4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -6992,9 +7087,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9794f69ad475e76c057e326175d3088509649e3aed98473106b9fe94ba59424" +checksum = "2f925d575b468e88b079faf590a8dd0c9c99e2ec29e9bab663ceb8b45056312f" dependencies = [ "httpdate", "native-tls", @@ -7012,9 +7107,9 @@ dependencies = [ [[package]] name = "sentry-actix" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0fee202934063ace4f1d1d063113b8982293762628e563a2d2fba08fb20b110" +checksum = "18bac0f6b8621fa0f85e298901e51161205788322e1a995e3764329020368058" dependencies = [ "actix-http", "actix-web", @@ -7025,9 +7120,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e81137ad53b8592bd0935459ad74c0376053c40084aa170451e74eeea8dbc6c3" +checksum = "6cb1ef7534f583af20452b1b1bf610a60ed9c8dd2d8485e7bd064efc556a78fb" dependencies = [ "backtrace", "regex", @@ -7036,9 +7131,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb403c66cc2651a01b9bacda2e7c22cd51f7e8f56f206aa4310147eb3259282" +checksum = "ebd6be899d9938390b6d1ec71e2f53bd9e57b6a9d8b1d5b049e5c364e7da9078" dependencies = [ "hostname", "libc", @@ -7050,9 +7145,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc409727ae90765ca8ea76fe6c949d6f159a11d02e130b357fa652ee9efcada" +checksum = "26ab054c34b87f96c3e4701bea1888317cde30cc7e4a6136d2c48454ab96661c" dependencies = [ "rand 0.9.2", "sentry-types", @@ -7063,9 +7158,9 @@ dependencies = [ [[package]] name = "sentry-debug-images" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a2778a222fd90ebb01027c341a72f8e24b0c604c6126504a4fe34e5500e646" +checksum = "5637ec550dc6f8c49a711537950722d3fc4baa6fd433c371912104eaff31e2a5" dependencies = [ "findshlibs", "sentry-core", @@ -7073,9 +7168,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df79f4e1e72b2a8b75a0ebf49e78709ceb9b3f0b451f13adc92a0361b0aaabe" +checksum = "3f02c7162f7b69b8de872b439d4696dc1d65f80b13ddd3c3831723def4756b63" dependencies = [ "sentry-backtrace", "sentry-core", @@ -7083,9 +7178,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2046f527fd4b75e0b6ab3bd656c67dce42072f828dc4d03c206d15dca74a93" +checksum = "e1dd47df349a80025819f3d25c3d2f751df705d49c65a4cdc0f130f700972a48" dependencies = [ "bitflags 2.10.0", "sentry-backtrace", @@ -7096,16 +7191,16 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b9b4e4c03a4d3643c18c78b8aa91d2cbee5da047d2fa0ca4bb29bc67e6c55c" +checksum = "eecbd63e9d15a26a40675ed180d376fcb434635d2e33de1c24003f61e3e2230d" dependencies = [ "debugid", "hex", "rand 0.9.2", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "url", "uuid", @@ -7138,7 +7233,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7149,7 +7244,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7159,7 +7254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" dependencies = [ "form_urlencoded", - "indexmap 2.12.0", + "indexmap 2.13.0", "itoa", "ryu", "serde_core", @@ -7167,16 +7262,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -7198,16 +7293,16 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -7232,9 +7327,9 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.0.4", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -7250,7 +7345,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7259,7 +7354,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -7268,9 +7363,9 @@ dependencies = [ [[package]] name = "serial2" -version = "0.2.31" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26e1e5956803a69ddd72ce2de337b577898801528749565def03515f82bad5bb" +checksum = "8cc76fa68e25e771492ca1e3c53d447ef0be3093e05cd3b47f4b712ba10c6f3c" dependencies = [ "cfg-if", "libc", @@ -7279,11 +7374,12 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" dependencies = [ - "futures", + "futures-executor", + "futures-util", "log", "once_cell", "parking_lot", @@ -7293,13 +7389,13 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7351,9 +7447,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -7373,9 +7469,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -7384,10 +7480,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -7403,9 +7500,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simdutf8" @@ -7421,15 +7518,15 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -7468,9 +7565,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -7525,9 +7622,9 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "hashlink", - "indexmap 2.12.0", + "indexmap 2.13.0", "log", "memchr", "once_cell", @@ -7537,7 +7634,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "tokio-stream", @@ -7557,7 +7654,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7580,7 +7677,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.104", + "syn 2.0.114", "tokio", "url", ] @@ -7623,7 +7720,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing", "uuid", @@ -7663,7 +7760,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing", "uuid", @@ -7690,7 +7787,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing", "url", @@ -7712,9 +7809,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "starlark" @@ -7765,7 +7862,7 @@ dependencies = [ "dupe", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7878,7 +7975,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7890,7 +7987,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7931,9 +8028,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -7957,7 +8054,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -7998,15 +8095,15 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", - "windows-sys 0.61.1", + "rustix 1.1.3", + "windows-sys 0.61.2", ] [[package]] @@ -8031,12 +8128,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix 1.1.3", + "windows-sys 0.60.2", ] [[package]] @@ -8063,7 +8160,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8074,7 +8171,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "test-case-core", ] @@ -8097,7 +8194,7 @@ checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8131,11 +8228,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -8146,18 +8243,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8180,14 +8277,14 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg 0.4.19", + "zune-jpeg 0.4.21", ] [[package]] name = "time" -version = "0.3.44" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", @@ -8195,22 +8292,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -8239,11 +8336,12 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -8274,9 +8372,9 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -8300,7 +8398,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8315,9 +8413,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -8387,12 +8485,12 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.5" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "indexmap 2.12.0", - "serde", + "indexmap 2.13.0", + "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -8415,7 +8513,7 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "winnow", @@ -8427,7 +8525,7 @@ version = "0.24.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c740b185920170a6d9191122cafef7010bd6270a3824594bff6784c04d7f09e" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "toml_writer", @@ -8451,14 +8549,14 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" dependencies = [ "async-trait", "base64", "bytes", - "http 1.3.1", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -8479,9 +8577,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" dependencies = [ "bytes", "prost", @@ -8490,13 +8588,13 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.0", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -8509,14 +8607,14 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body", "iri-string", "pin-project-lite", @@ -8551,12 +8649,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 1.0.69", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -8569,7 +8667,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8605,16 +8703,13 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" dependencies = [ "js-sys", "opentelemetry", - "opentelemetry_sdk", - "rustversion", "smallvec", - "thiserror 2.0.17", "tracing", "tracing-core", "tracing-log", @@ -8658,7 +8753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8669,7 +8764,7 @@ checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" dependencies = [ "cc", "regex", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", "serde_json", "streaming-iterator", "tree-sitter-language", @@ -8677,9 +8772,9 @@ dependencies = [ [[package]] name = "tree-sitter-bash" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871b0606e667e98a1237ebdc1b0d7056e0aebfdc3141d12b399865d4cb6ed8a6" +checksum = "9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062" dependencies = [ "cc", "tree-sitter-language", @@ -8693,26 +8788,25 @@ checksum = "adc5f880ad8d8f94e88cb81c3557024cf1a8b75e3b504c50481ed4f5a6006ff3" dependencies = [ "regex", "streaming-iterator", - "thiserror 2.0.17", + "thiserror 2.0.18", "tree-sitter", ] [[package]] name = "tree-sitter-language" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8" +checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" [[package]] name = "tree_magic_mini" -version = "3.2.0" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f943391d896cdfe8eec03a04d7110332d445be7df856db382dd96a730667562c" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" dependencies = [ "memchr", - "nom 7.1.3", - "once_cell", - "petgraph", + "nom 8.0.0", + "petgraph 0.8.3", ] [[package]] @@ -8728,7 +8822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424" dependencies = [ "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "ts-rs-macros", "uuid", ] @@ -8741,7 +8835,7 @@ checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "termcolor", ] @@ -8752,22 +8846,22 @@ source = "git+https://github.com/JakkuSakura/tungstenite-rs?rev=f514de8644821113 dependencies = [ "bytes", "data-encoding", - "http 1.3.1", + "http 1.4.0", "httparse", "log", "rand 0.9.2", "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "uds_windows" @@ -8797,9 +8891,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -8809,9 +8903,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-linebreak" @@ -8905,21 +8999,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" dependencies = [ "base64", - "http 1.3.1", + "http 1.4.0", "httparse", "log", ] [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -8948,13 +9043,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", - "serde", + "serde_core", "sha1_smol", "wasm-bindgen", ] @@ -9033,12 +9128,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -9049,37 +9144,25 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -9088,9 +9171,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9098,22 +9181,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.104", - "wasm-bindgen-backend", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -9133,34 +9216,34 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" dependencies = [ "cc", "downcast-rs", - "rustix 1.0.8", + "rustix 1.1.3", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" -version = "0.31.11" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" dependencies = [ "bitflags 2.10.0", - "rustix 1.0.8", + "rustix 1.1.3", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.32.9" +version = "0.32.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -9170,9 +9253,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -9183,29 +9266,29 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" dependencies = [ "proc-macro2", - "quick-xml 0.37.5", + "quick-xml", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" dependencies = [ "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -9239,9 +9322,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" dependencies = [ "rustls-pki-types", ] @@ -9252,23 +9335,23 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] [[package]] name = "weezl" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "which" @@ -9277,7 +9360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix 1.0.8", + "rustix 1.1.3", "winsafe", ] @@ -9303,7 +9386,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9330,11 +9413,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9355,24 +9438,23 @@ dependencies = [ [[package]] name = "windows" -version = "0.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core 0.62.2", "windows-future", - "windows-link 0.1.3", "windows-numerics", ] [[package]] name = "windows-collections" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -9390,25 +9472,25 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] name = "windows-future" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core 0.62.2", + "windows-link", "windows-threading", ] @@ -9420,18 +9502,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -9442,51 +9524,45 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core 0.62.2", + "windows-link", ] [[package]] name = "windows-registry" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -9500,11 +9576,11 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -9519,11 +9595,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -9568,16 +9644,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -9628,27 +9704,28 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows-threading" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -9671,9 +9748,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -9695,9 +9772,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -9719,9 +9796,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -9731,9 +9808,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -9755,9 +9832,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -9779,9 +9856,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -9803,9 +9880,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -9827,15 +9904,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -9890,7 +9967,7 @@ dependencies = [ "base64", "deadpool", "futures", - "http 1.3.1", + "http 1.4.0", "http-body-util", "hyper", "hyper-util", @@ -9904,26 +9981,22 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.10.0", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "wl-clipboard-rs" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5ff8d0e60065f549fafd9d6cb626203ea64a798186c80d8e7df4f8af56baeb" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" dependencies = [ "libc", "log", "os_pipe", - "rustix 0.38.44", - "tempfile", - "thiserror 2.0.17", + "rustix 1.1.3", + "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", "wayland-client", @@ -9939,20 +10012,20 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "gethostname", - "rustix 0.38.44", + "rustix 1.1.3", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xdg-home" @@ -9972,11 +10045,10 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -9984,13 +10056,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "synstructure", ] @@ -10041,7 +10113,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "zvariant_utils", ] @@ -10058,22 +10130,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10093,7 +10165,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "synstructure", ] @@ -10108,20 +10180,20 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -10142,15 +10214,21 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" + [[package]] name = "zstd" version = "0.13.3" @@ -10187,26 +10265,26 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-core" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" [[package]] name = "zune-jpeg" -version = "0.4.19" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" dependencies = [ "zune-core 0.4.12", ] [[package]] name = "zune-jpeg" -version = "0.5.5" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e" +checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe" dependencies = [ - "zune-core 0.5.0", + "zune-core 0.5.1", ] [[package]] @@ -10231,7 +10309,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "zvariant_utils", ] @@ -10243,5 +10321,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 84210838fc..3171c8a96c 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -50,6 +50,7 @@ members = [ "codex-client", "codex-api", "state", + "codex-experimental-api-macros", ] resolver = "2" @@ -81,6 +82,7 @@ codex-common = { path = "common" } codex-core = { path = "core" } codex-exec = { path = "exec" } codex-execpolicy = { path = "execpolicy" } +codex-experimental-api-macros = { path = "codex-experimental-api-macros" } codex-feedback = { path = "feedback" } codex-file-search = { path = "file-search" } codex-git = { path = "utils/git" } @@ -155,6 +157,7 @@ image = { version = "^0.25.9", default-features = false } include_dir = "0.7.4" indexmap = "2.12.0" insta = "1.46.0" +inventory = "0.3.19" itertools = "0.14.0" keyring = { version = "3.6", default-features = false } landlock = "0.4.4" diff --git a/codex-rs/app-server-protocol/Cargo.toml b/codex-rs/app-server-protocol/Cargo.toml index b3fc75bc50..2788801051 100644 --- a/codex-rs/app-server-protocol/Cargo.toml +++ b/codex-rs/app-server-protocol/Cargo.toml @@ -15,6 +15,7 @@ workspace = true anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } codex-protocol = { workspace = true } +codex-experimental-api-macros = { workspace = true } codex-utils-absolute-path = { workspace = true } mcp-types = { workspace = true } schemars = { workspace = true } @@ -23,6 +24,7 @@ serde_json = { workspace = true } strum_macros = { workspace = true } thiserror = { workspace = true } ts-rs = { workspace = true } +inventory = { workspace = true } uuid = { workspace = true, features = ["serde", "v7"] } [dev-dependencies] diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 5de02b85f0..85e5940bc3 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -176,10 +176,6 @@ ], "type": "object" }, - "CollaborationModeListParams": { - "description": "EXPERIMENTAL - list collaboration mode presets.", - "type": "object" - }, "CommandExecParams": { "properties": { "command": { @@ -678,8 +674,29 @@ ], "type": "object" }, + "InitializeCapabilities": { + "description": "Client-declared capabilities negotiated during initialize.", + "properties": { + "experimentalApi": { + "default": false, + "description": "Opt into receiving experimental API methods and fields.", + "type": "boolean" + } + }, + "type": "object" + }, "InitializeParams": { "properties": { + "capabilities": { + "anyOf": [ + { + "$ref": "#/definitions/InitializeCapabilities" + }, + { + "type": "null" + } + ] + }, "clientInfo": { "$ref": "#/definitions/ClientInfo" } @@ -2551,15 +2568,6 @@ "null" ] }, - "dynamicTools": { - "items": { - "$ref": "#/definitions/DynamicToolSpec" - }, - "type": [ - "array", - "null" - ] - }, "ephemeral": { "type": [ "boolean", @@ -3434,31 +3442,6 @@ "title": "Model/listRequest", "type": "object" }, - { - "description": "EXPERIMENTAL - list collaboration mode presets.", - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "collaborationMode/list" - ], - "title": "CollaborationMode/listRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/CollaborationModeListParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "CollaborationMode/listRequest", - "type": "object" - }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 413f1348e5..aca840f6cb 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -942,31 +942,6 @@ "title": "Model/listRequest", "type": "object" }, - { - "description": "EXPERIMENTAL - list collaboration mode presets.", - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "collaborationMode/list" - ], - "title": "CollaborationMode/listRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/CollaborationModeListParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "CollaborationMode/listRequest", - "type": "object" - }, { "properties": { "id": { @@ -5463,9 +5438,30 @@ ], "type": "object" }, + "InitializeCapabilities": { + "description": "Client-declared capabilities negotiated during initialize.", + "properties": { + "experimentalApi": { + "default": false, + "description": "Opt into receiving experimental API methods and fields.", + "type": "boolean" + } + }, + "type": "object" + }, "InitializeParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "capabilities": { + "anyOf": [ + { + "$ref": "#/definitions/InitializeCapabilities" + }, + { + "type": "null" + } + ] + }, "clientInfo": { "$ref": "#/definitions/ClientInfo" } @@ -10408,29 +10404,6 @@ ], "type": "object" }, - "CollaborationModeListParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "EXPERIMENTAL - list collaboration mode presets.", - "title": "CollaborationModeListParams", - "type": "object" - }, - "CollaborationModeListResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "EXPERIMENTAL - collaboration mode presets response.", - "properties": { - "data": { - "items": { - "$ref": "#/definitions/v2/CollaborationModeMask" - }, - "type": "array" - } - }, - "required": [ - "data" - ], - "title": "CollaborationModeListResponse", - "type": "object" - }, "CollaborationModeMask": { "description": "A mask for collaboration mode settings, allowing partial updates. All fields except `name` are optional, enabling selective updates.", "properties": { @@ -15319,15 +15292,6 @@ "null" ] }, - "dynamicTools": { - "items": { - "$ref": "#/definitions/v2/DynamicToolSpec" - }, - "type": [ - "array", - "null" - ] - }, "ephemeral": { "type": [ "boolean", diff --git a/codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json b/codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json index 06dd68dd59..dd71c717f1 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v1/InitializeParams.json @@ -21,9 +21,30 @@ "version" ], "type": "object" + }, + "InitializeCapabilities": { + "description": "Client-declared capabilities negotiated during initialize.", + "properties": { + "experimentalApi": { + "default": false, + "description": "Opt into receiving experimental API methods and fields.", + "type": "boolean" + } + }, + "type": "object" } }, "properties": { + "capabilities": { + "anyOf": [ + { + "$ref": "#/definitions/InitializeCapabilities" + }, + { + "type": "null" + } + ] + }, "clientInfo": { "$ref": "#/definitions/ClientInfo" } diff --git a/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json b/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json deleted file mode 100644 index bbac0d4f10..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListParams.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "EXPERIMENTAL - list collaboration mode presets.", - "title": "CollaborationModeListParams", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json deleted file mode 100644 index 7da8fbaa47..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/CollaborationModeListResponse.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "CollaborationModeMask": { - "description": "A mask for collaboration mode settings, allowing partial updates. All fields except `name` are optional, enabling selective updates.", - "properties": { - "developer_instructions": { - "type": [ - "string", - "null" - ] - }, - "mode": { - "anyOf": [ - { - "$ref": "#/definitions/ModeKind" - }, - { - "type": "null" - } - ] - }, - "model": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "reasoning_effort": { - "anyOf": [ - { - "anyOf": [ - { - "$ref": "#/definitions/ReasoningEffort" - }, - { - "type": "null" - } - ] - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "ModeKind": { - "description": "Initial collaboration mode to use when the TUI starts.", - "enum": [ - "plan", - "code", - "pair_programming", - "execute", - "custom" - ], - "type": "string" - }, - "ReasoningEffort": { - "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", - "enum": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" - ], - "type": "string" - } - }, - "description": "EXPERIMENTAL - collaboration mode presets response.", - "properties": { - "data": { - "items": { - "$ref": "#/definitions/CollaborationModeMask" - }, - "type": "array" - } - }, - "required": [ - "data" - ], - "title": "CollaborationModeListResponse", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index d002d03a19..03bed7f93c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -79,15 +79,6 @@ "null" ] }, - "dynamicTools": { - "items": { - "$ref": "#/definitions/DynamicToolSpec" - }, - "type": [ - "array", - "null" - ] - }, "ephemeral": { "type": [ "boolean", diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index c323f1e03e..b65d9ee931 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -23,7 +23,6 @@ import type { SendUserTurnParams } from "./SendUserTurnParams"; import type { SetDefaultModelParams } from "./SetDefaultModelParams"; import type { AppsListParams } from "./v2/AppsListParams"; import type { CancelLoginAccountParams } from "./v2/CancelLoginAccountParams"; -import type { CollaborationModeListParams } from "./v2/CollaborationModeListParams"; import type { CommandExecParams } from "./v2/CommandExecParams"; import type { ConfigBatchWriteParams } from "./v2/ConfigBatchWriteParams"; import type { ConfigReadParams } from "./v2/ConfigReadParams"; @@ -53,4 +52,4 @@ import type { TurnStartParams } from "./v2/TurnStartParams"; /** * Request from the client to the server. */ -export type ClientRequest = { "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "collaborationMode/list", id: RequestId, params: CollaborationModeListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InitializeCapabilities.ts b/codex-rs/app-server-protocol/schema/typescript/InitializeCapabilities.ts new file mode 100644 index 0000000000..24f5302627 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/InitializeCapabilities.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Client-declared capabilities negotiated during initialize. + */ +export type InitializeCapabilities = { +/** + * Opt into receiving experimental API methods and fields. + */ +experimentalApi: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts b/codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts index 76c2bc10ff..e48c5ee7b5 100644 --- a/codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/InitializeParams.ts @@ -2,5 +2,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ClientInfo } from "./ClientInfo"; +import type { InitializeCapabilities } from "./InitializeCapabilities"; -export type InitializeParams = { clientInfo: ClientInfo, }; +export type InitializeParams = { clientInfo: ClientInfo, capabilities: InitializeCapabilities | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 7a4a9e1d11..fafc6aad9d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -93,6 +93,7 @@ export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse"; export type { GitSha } from "./GitSha"; export type { HistoryEntry } from "./HistoryEntry"; export type { ImageContent } from "./ImageContent"; +export type { InitializeCapabilities } from "./InitializeCapabilities"; export type { InitializeParams } from "./InitializeParams"; export type { InitializeResponse } from "./InitializeResponse"; export type { InputItem } from "./InputItem"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts deleted file mode 100644 index 37e8f792d9..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListParams.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * EXPERIMENTAL - list collaboration mode presets. - */ -export type CollaborationModeListParams = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts deleted file mode 100644 index 6b741a36ac..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/CollaborationModeListResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { CollaborationModeMask } from "../CollaborationModeMask"; - -/** - * EXPERIMENTAL - collaboration mode presets response. - */ -export type CollaborationModeListResponse = { data: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts index bb7316706e..3b4c205901 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts @@ -4,14 +4,12 @@ import type { Personality } from "../Personality"; import type { JsonValue } from "../serde_json/JsonValue"; import type { AskForApproval } from "./AskForApproval"; -import type { DynamicToolSpec } from "./DynamicToolSpec"; import type { SandboxMode } from "./SandboxMode"; -export type ThreadStartParams = { model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, personality: Personality | null, ephemeral: boolean | null, dynamicTools: Array | null, -/** +export type ThreadStartParams = {model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, personality: Personality | null, ephemeral: boolean | null, /** * If true, opt into emitting raw response items on the event stream. * * This is for internal use only (e.g. Codex Cloud). * (TODO): Figure out a better way to categorize internal / experimental events & protocols. */ -experimentalRawEvents: boolean, }; +experimentalRawEvents: boolean}; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 4f19e3e902..10b60a45fd 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -22,8 +22,6 @@ export type { CollabAgentState } from "./CollabAgentState"; export type { CollabAgentStatus } from "./CollabAgentStatus"; export type { CollabAgentTool } from "./CollabAgentTool"; export type { CollabAgentToolCallStatus } from "./CollabAgentToolCallStatus"; -export type { CollaborationModeListParams } from "./CollaborationModeListParams"; -export type { CollaborationModeListResponse } from "./CollaborationModeListResponse"; export type { CommandAction } from "./CommandAction"; export type { CommandExecParams } from "./CommandExecParams"; export type { CommandExecResponse } from "./CommandExecResponse"; diff --git a/codex-rs/app-server-protocol/src/experimental_api.rs b/codex-rs/app-server-protocol/src/experimental_api.rs new file mode 100644 index 0000000000..05f45600d9 --- /dev/null +++ b/codex-rs/app-server-protocol/src/experimental_api.rs @@ -0,0 +1,70 @@ +/// Marker trait for protocol types that can signal experimental usage. +pub trait ExperimentalApi { + /// Returns a short reason identifier when an experimental method or field is + /// used, or `None` when the value is entirely stable. + fn experimental_reason(&self) -> Option<&'static str>; +} + +/// Describes an experimental field on a specific type. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ExperimentalField { + pub type_name: &'static str, + pub field_name: &'static str, + /// Stable identifier returned when this field is used. + /// Convention: `` for method-level gates or `.` for + /// field-level gates. + pub reason: &'static str, +} + +inventory::collect!(ExperimentalField); + +/// Returns all experimental fields registered across the protocol types. +pub fn experimental_fields() -> Vec<&'static ExperimentalField> { + inventory::iter::.into_iter().collect() +} + +/// Constructs a consistent error message for experimental gating. +pub fn experimental_required_message(reason: &str) -> String { + format!("{reason} requires experimentalApi capability") +} + +#[cfg(test)] +mod tests { + use super::ExperimentalApi as ExperimentalApiTrait; + use codex_experimental_api_macros::ExperimentalApi; + use pretty_assertions::assert_eq; + + #[allow(dead_code)] + #[derive(ExperimentalApi)] + enum EnumVariantShapes { + #[experimental("enum/unit")] + Unit, + #[experimental("enum/tuple")] + Tuple(u8), + #[experimental("enum/named")] + Named { + value: u8, + }, + StableTuple(u8), + } + + #[test] + fn derive_supports_all_enum_variant_shapes() { + assert_eq!( + ExperimentalApiTrait::experimental_reason(&EnumVariantShapes::Unit), + Some("enum/unit") + ); + assert_eq!( + ExperimentalApiTrait::experimental_reason(&EnumVariantShapes::Tuple(1)), + Some("enum/tuple") + ); + assert_eq!( + ExperimentalApiTrait::experimental_reason(&EnumVariantShapes::Named { value: 1 }), + Some("enum/named") + ); + assert_eq!( + ExperimentalApiTrait::experimental_reason(&EnumVariantShapes::StableTuple(1)), + None + ); + } +} diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index a60c1be624..7878f72c04 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -2,6 +2,7 @@ use crate::ClientNotification; use crate::ClientRequest; use crate::ServerNotification; use crate::ServerRequest; +use crate::experimental_api::experimental_fields; use crate::export_client_notification_schemas; use crate::export_client_param_schemas; use crate::export_client_response_schemas; @@ -10,6 +11,9 @@ use crate::export_server_notification_schemas; use crate::export_server_param_schemas; use crate::export_server_response_schemas; use crate::export_server_responses; +use crate::protocol::common::EXPERIMENTAL_CLIENT_METHOD_PARAM_TYPES; +use crate::protocol::common::EXPERIMENTAL_CLIENT_METHOD_RESPONSE_TYPES; +use crate::protocol::common::EXPERIMENTAL_CLIENT_METHODS; use anyhow::Context; use anyhow::Result; use anyhow::anyhow; @@ -67,6 +71,7 @@ pub struct GenerateTsOptions { pub generate_indices: bool, pub ensure_headers: bool, pub run_prettier: bool, + pub experimental_api: bool, } impl Default for GenerateTsOptions { @@ -75,6 +80,7 @@ impl Default for GenerateTsOptions { generate_indices: true, ensure_headers: true, run_prettier: true, + experimental_api: false, } } } @@ -100,6 +106,10 @@ pub fn generate_ts_with_options( export_server_responses(out_dir)?; ServerNotification::export_all_to(out_dir)?; + if !options.experimental_api { + filter_experimental_ts(out_dir)?; + } + if options.generate_indices { generate_index_ts(out_dir)?; generate_index_ts(&v2_out_dir)?; @@ -140,8 +150,12 @@ pub fn generate_ts_with_options( } pub fn generate_json(out_dir: &Path) -> Result<()> { + generate_json_with_experimental(out_dir, false) +} + +pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -> Result<()> { ensure_dir(out_dir)?; - let envelope_emitters: &[JsonSchemaEmitter] = &[ + let envelope_emitters: Vec = vec![ |d| write_json_schema_with_return::(d, "RequestId"), |d| write_json_schema_with_return::(d, "JSONRPCMessage"), |d| write_json_schema_with_return::(d, "JSONRPCRequest"), @@ -157,7 +171,7 @@ pub fn generate_json(out_dir: &Path) -> Result<()> { ]; let mut schemas: Vec = Vec::new(); - for emit in envelope_emitters { + for emit in &envelope_emitters { schemas.push(emit(out_dir)?); } @@ -168,15 +182,654 @@ pub fn generate_json(out_dir: &Path) -> Result<()> { schemas.extend(export_client_notification_schemas(out_dir)?); schemas.extend(export_server_notification_schemas(out_dir)?); - let bundle = build_schema_bundle(schemas)?; + let mut bundle = build_schema_bundle(schemas)?; + if !experimental_api { + filter_experimental_schema(&mut bundle)?; + } write_pretty_json( out_dir.join("codex_app_server_protocol.schemas.json"), &bundle, )?; + if !experimental_api { + filter_experimental_json_files(out_dir)?; + } + Ok(()) } +fn filter_experimental_ts(out_dir: &Path) -> Result<()> { + let registered_fields = experimental_fields(); + let experimental_method_types = experimental_method_types(); + // Most generated TS files are filtered by schema processing, but + // `ClientRequest.ts` and any type with `#[experimental(...)]` fields need + // direct post-processing because they encode method/field information in + // file-local unions/interfaces. + filter_client_request_ts(out_dir, EXPERIMENTAL_CLIENT_METHODS)?; + filter_experimental_type_fields_ts(out_dir, ®istered_fields)?; + remove_generated_type_files(out_dir, &experimental_method_types, "ts")?; + Ok(()) +} + +/// Removes union arms from `ClientRequest.ts` for methods marked experimental. +fn filter_client_request_ts(out_dir: &Path, experimental_methods: &[&str]) -> Result<()> { + let path = out_dir.join("ClientRequest.ts"); + if !path.exists() { + return Ok(()); + } + let mut content = + fs::read_to_string(&path).with_context(|| format!("Failed to read {}", path.display()))?; + + let Some((prefix, body, suffix)) = split_type_alias(&content) else { + return Ok(()); + }; + let experimental_methods: HashSet<&str> = experimental_methods + .iter() + .copied() + .filter(|method| !method.is_empty()) + .collect(); + let arms = split_top_level(&body, '|'); + let filtered_arms: Vec = arms + .into_iter() + .filter(|arm| { + extract_method_from_arm(arm) + .is_none_or(|method| !experimental_methods.contains(method.as_str())) + }) + .collect(); + let new_body = filtered_arms.join(" | "); + content = format!("{prefix}{new_body}{suffix}"); + content = prune_unused_type_imports(content, &new_body); + + fs::write(&path, content).with_context(|| format!("Failed to write {}", path.display()))?; + Ok(()) +} + +/// Removes experimental properties from generated TypeScript type files. +fn filter_experimental_type_fields_ts( + out_dir: &Path, + experimental_fields: &[&'static crate::experimental_api::ExperimentalField], +) -> Result<()> { + let mut fields_by_type_name: HashMap> = HashMap::new(); + for field in experimental_fields { + fields_by_type_name + .entry(field.type_name.to_string()) + .or_default() + .insert(field.field_name.to_string()); + } + if fields_by_type_name.is_empty() { + return Ok(()); + } + + for path in ts_files_in_recursive(out_dir)? { + let Some(type_name) = path.file_stem().and_then(|stem| stem.to_str()) else { + continue; + }; + let Some(experimental_field_names) = fields_by_type_name.get(type_name) else { + continue; + }; + filter_experimental_fields_in_ts_file(&path, experimental_field_names)?; + } + + Ok(()) +} + +fn filter_experimental_fields_in_ts_file( + path: &Path, + experimental_field_names: &HashSet, +) -> Result<()> { + let mut content = + fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; + let Some((open_brace, close_brace)) = type_body_brace_span(&content) else { + return Ok(()); + }; + let inner = &content[open_brace + 1..close_brace]; + let fields = split_top_level_multi(inner, &[',', ';']); + let filtered_fields: Vec = fields + .into_iter() + .filter(|field| { + let field = strip_leading_block_comments(field); + parse_property_name(field) + .is_none_or(|name| !experimental_field_names.contains(name.as_str())) + }) + .collect(); + let new_inner = filtered_fields.join(", "); + let prefix = &content[..open_brace + 1]; + let suffix = &content[close_brace..]; + content = format!("{prefix}{new_inner}{suffix}"); + content = prune_unused_type_imports(content, &new_inner); + fs::write(path, content).with_context(|| format!("Failed to write {}", path.display()))?; + Ok(()) +} + +fn filter_experimental_schema(bundle: &mut Value) -> Result<()> { + let registered_fields = experimental_fields(); + filter_experimental_fields_in_root(bundle, ®istered_fields); + filter_experimental_fields_in_definitions(bundle, ®istered_fields); + prune_experimental_methods(bundle, EXPERIMENTAL_CLIENT_METHODS); + remove_experimental_method_type_definitions(bundle); + Ok(()) +} + +fn filter_experimental_fields_in_root( + schema: &mut Value, + experimental_fields: &[&'static crate::experimental_api::ExperimentalField], +) { + let Some(title) = schema.get("title").and_then(Value::as_str) else { + return; + }; + let title = title.to_string(); + + for field in experimental_fields { + if title != field.type_name { + continue; + } + remove_property_from_schema(schema, field.field_name); + } +} + +fn filter_experimental_fields_in_definitions( + bundle: &mut Value, + experimental_fields: &[&'static crate::experimental_api::ExperimentalField], +) { + let Some(definitions) = bundle.get_mut("definitions").and_then(Value::as_object_mut) else { + return; + }; + + filter_experimental_fields_in_definitions_map(definitions, experimental_fields); +} + +fn filter_experimental_fields_in_definitions_map( + definitions: &mut Map, + experimental_fields: &[&'static crate::experimental_api::ExperimentalField], +) { + for (def_name, def_schema) in definitions.iter_mut() { + if is_namespace_map(def_schema) { + if let Some(namespace_defs) = def_schema.as_object_mut() { + filter_experimental_fields_in_definitions_map(namespace_defs, experimental_fields); + } + continue; + } + + for field in experimental_fields { + if !definition_matches_type(def_name, field.type_name) { + continue; + } + remove_property_from_schema(def_schema, field.field_name); + } + } +} + +fn is_namespace_map(value: &Value) -> bool { + let Value::Object(map) = value else { + return false; + }; + + if map.keys().any(|key| key.starts_with('$')) { + return false; + } + + let looks_like_schema = map.contains_key("type") + || map.contains_key("properties") + || map.contains_key("anyOf") + || map.contains_key("oneOf") + || map.contains_key("allOf"); + + !looks_like_schema && map.values().all(Value::is_object) +} + +fn definition_matches_type(def_name: &str, type_name: &str) -> bool { + def_name == type_name || def_name.ends_with(&format!("::{type_name}")) +} + +fn remove_property_from_schema(schema: &mut Value, field_name: &str) { + if let Some(properties) = schema.get_mut("properties").and_then(Value::as_object_mut) { + properties.remove(field_name); + } + + if let Some(required) = schema.get_mut("required").and_then(Value::as_array_mut) { + required.retain(|entry| entry.as_str() != Some(field_name)); + } + + if let Some(inner_schema) = schema.get_mut("schema") { + remove_property_from_schema(inner_schema, field_name); + } +} + +fn prune_experimental_methods(bundle: &mut Value, experimental_methods: &[&str]) { + let experimental_methods: HashSet<&str> = experimental_methods + .iter() + .copied() + .filter(|method| !method.is_empty()) + .collect(); + prune_experimental_methods_inner(bundle, &experimental_methods); +} + +fn prune_experimental_methods_inner(value: &mut Value, experimental_methods: &HashSet<&str>) { + match value { + Value::Array(items) => { + items.retain(|item| !is_experimental_method_variant(item, experimental_methods)); + for item in items { + prune_experimental_methods_inner(item, experimental_methods); + } + } + Value::Object(map) => { + for entry in map.values_mut() { + prune_experimental_methods_inner(entry, experimental_methods); + } + } + Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {} + } +} + +fn is_experimental_method_variant(value: &Value, experimental_methods: &HashSet<&str>) -> bool { + let Value::Object(map) = value else { + return false; + }; + let Some(properties) = map.get("properties").and_then(Value::as_object) else { + return false; + }; + let Some(method_schema) = properties.get("method").and_then(Value::as_object) else { + return false; + }; + + if let Some(method) = method_schema.get("const").and_then(Value::as_str) { + return experimental_methods.contains(method); + } + + if let Some(values) = method_schema.get("enum").and_then(Value::as_array) + && values.len() == 1 + && let Some(method) = values[0].as_str() + { + return experimental_methods.contains(method); + } + + false +} + +fn filter_experimental_json_files(out_dir: &Path) -> Result<()> { + for path in json_files_in_recursive(out_dir)? { + let mut value = read_json_value(&path)?; + filter_experimental_schema(&mut value)?; + write_pretty_json(path, &value)?; + } + let experimental_method_types = experimental_method_types(); + remove_generated_type_files(out_dir, &experimental_method_types, "json")?; + Ok(()) +} + +fn experimental_method_types() -> HashSet { + let mut type_names = HashSet::new(); + collect_experimental_type_names(EXPERIMENTAL_CLIENT_METHOD_PARAM_TYPES, &mut type_names); + collect_experimental_type_names(EXPERIMENTAL_CLIENT_METHOD_RESPONSE_TYPES, &mut type_names); + type_names +} + +fn collect_experimental_type_names(entries: &[&str], out: &mut HashSet) { + for entry in entries { + let trimmed = entry.trim(); + if trimmed.is_empty() { + continue; + } + let name = trimmed.rsplit("::").next().unwrap_or(trimmed); + if !name.is_empty() { + out.insert(name.to_string()); + } + } +} + +fn remove_generated_type_files( + out_dir: &Path, + type_names: &HashSet, + extension: &str, +) -> Result<()> { + for type_name in type_names { + for subdir in ["", "v1", "v2"] { + let path = if subdir.is_empty() { + out_dir.join(format!("{type_name}.{extension}")) + } else { + out_dir + .join(subdir) + .join(format!("{type_name}.{extension}")) + }; + if path.exists() { + fs::remove_file(&path) + .with_context(|| format!("Failed to remove {}", path.display()))?; + } + } + } + Ok(()) +} + +fn remove_experimental_method_type_definitions(bundle: &mut Value) { + let type_names = experimental_method_types(); + let Some(definitions) = bundle.get_mut("definitions").and_then(Value::as_object_mut) else { + return; + }; + remove_experimental_method_type_definitions_map(definitions, &type_names); +} + +fn remove_experimental_method_type_definitions_map( + definitions: &mut Map, + experimental_type_names: &HashSet, +) { + let keys_to_remove: Vec = definitions + .keys() + .filter(|def_name| { + experimental_type_names + .iter() + .any(|type_name| definition_matches_type(def_name, type_name)) + }) + .cloned() + .collect(); + for key in keys_to_remove { + definitions.remove(&key); + } + + for value in definitions.values_mut() { + if !is_namespace_map(value) { + continue; + } + if let Some(namespace_defs) = value.as_object_mut() { + remove_experimental_method_type_definitions_map( + namespace_defs, + experimental_type_names, + ); + } + } +} + +fn prune_unused_type_imports(content: String, type_alias_body: &str) -> String { + let trailing_newline = content.ends_with('\n'); + let mut lines = Vec::new(); + for line in content.lines() { + if let Some(type_name) = parse_imported_type_name(line) + && !type_alias_body.contains(type_name) + { + continue; + } + lines.push(line); + } + + let mut rewritten = lines.join("\n"); + if trailing_newline { + rewritten.push('\n'); + } + rewritten +} + +fn parse_imported_type_name(line: &str) -> Option<&str> { + let line = line.trim(); + let rest = line.strip_prefix("import type {")?; + let (type_name, _) = rest.split_once("} from ")?; + let type_name = type_name.trim(); + if type_name.is_empty() || type_name.contains(',') || type_name.contains(" as ") { + return None; + } + Some(type_name) +} + +fn json_files_in_recursive(dir: &Path) -> Result> { + let mut out = Vec::new(); + let mut stack = vec![dir.to_path_buf()]; + while let Some(current) = stack.pop() { + for entry in fs::read_dir(¤t)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + stack.push(path); + continue; + } + if matches!(path.extension().and_then(|ext| ext.to_str()), Some("json")) { + out.push(path); + } + } + } + Ok(out) +} + +fn read_json_value(path: &Path) -> Result { + let content = + fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; + serde_json::from_str(&content).with_context(|| format!("Failed to parse {}", path.display())) +} + +fn split_type_alias(content: &str) -> Option<(String, String, String)> { + let eq_index = content.find('=')?; + let semi_index = content.rfind(';')?; + if semi_index <= eq_index { + return None; + } + let prefix = content[..eq_index + 1].to_string(); + let body = content[eq_index + 1..semi_index].to_string(); + let suffix = content[semi_index..].to_string(); + Some((prefix, body, suffix)) +} + +fn type_body_brace_span(content: &str) -> Option<(usize, usize)> { + if let Some(eq_index) = content.find('=') { + let after_eq = &content[eq_index + 1..]; + let (open_rel, close_rel) = find_top_level_brace_span(after_eq)?; + return Some((eq_index + 1 + open_rel, eq_index + 1 + close_rel)); + } + + const INTERFACE_MARKER: &str = "export interface"; + let interface_index = content.find(INTERFACE_MARKER)?; + let after_interface = &content[interface_index + INTERFACE_MARKER.len()..]; + let (open_rel, close_rel) = find_top_level_brace_span(after_interface)?; + Some(( + interface_index + INTERFACE_MARKER.len() + open_rel, + interface_index + INTERFACE_MARKER.len() + close_rel, + )) +} + +fn find_top_level_brace_span(input: &str) -> Option<(usize, usize)> { + let mut state = ScanState::default(); + let mut open_index = None; + for (index, ch) in input.char_indices() { + if !state.in_string() && ch == '{' && state.depth.is_top_level() { + open_index = Some(index); + } + state.observe(ch); + if !state.in_string() + && ch == '}' + && state.depth.is_top_level() + && let Some(open) = open_index + { + return Some((open, index)); + } + } + None +} + +fn split_top_level(input: &str, delimiter: char) -> Vec { + split_top_level_multi(input, &[delimiter]) +} + +fn split_top_level_multi(input: &str, delimiters: &[char]) -> Vec { + let mut state = ScanState::default(); + let mut start = 0usize; + let mut parts = Vec::new(); + for (index, ch) in input.char_indices() { + if !state.in_string() && state.depth.is_top_level() && delimiters.contains(&ch) { + let part = input[start..index].trim(); + if !part.is_empty() { + parts.push(part.to_string()); + } + start = index + ch.len_utf8(); + } + state.observe(ch); + } + let tail = input[start..].trim(); + if !tail.is_empty() { + parts.push(tail.to_string()); + } + parts +} + +fn extract_method_from_arm(arm: &str) -> Option { + let (open, close) = find_top_level_brace_span(arm)?; + let inner = &arm[open + 1..close]; + for field in split_top_level(inner, ',') { + let Some((name, value)) = parse_property(field.as_str()) else { + continue; + }; + if name != "method" { + continue; + } + let value = value.trim_start(); + let (literal, _) = parse_string_literal(value)?; + return Some(literal); + } + None +} + +fn parse_property(input: &str) -> Option<(String, &str)> { + let name = parse_property_name(input)?; + let colon_index = input.find(':')?; + Some((name, input[colon_index + 1..].trim_start())) +} + +fn strip_leading_block_comments(input: &str) -> &str { + let mut rest = input.trim_start(); + loop { + let Some(after_prefix) = rest.strip_prefix("/*") else { + return rest; + }; + let Some(end_rel) = after_prefix.find("*/") else { + return rest; + }; + rest = after_prefix[end_rel + 2..].trim_start(); + } +} + +fn parse_property_name(input: &str) -> Option { + let trimmed = input.trim_start(); + if trimmed.is_empty() { + return None; + } + if let Some((literal, consumed)) = parse_string_literal(trimmed) { + let rest = trimmed[consumed..].trim_start(); + if rest.starts_with(':') { + return Some(literal); + } + return None; + } + + let mut end = 0usize; + for (index, ch) in trimmed.char_indices() { + if !is_ident_char(ch) { + break; + } + end = index + ch.len_utf8(); + } + if end == 0 { + return None; + } + let name = &trimmed[..end]; + let rest = trimmed[end..].trim_start(); + let rest = if let Some(stripped) = rest.strip_prefix('?') { + stripped.trim_start() + } else { + rest + }; + if rest.starts_with(':') { + return Some(name.to_string()); + } + None +} + +fn parse_string_literal(input: &str) -> Option<(String, usize)> { + let mut chars = input.char_indices(); + let (start_index, quote) = chars.next()?; + if quote != '"' && quote != '\'' { + return None; + } + let mut escape = false; + for (index, ch) in chars { + if escape { + escape = false; + continue; + } + if ch == '\\' { + escape = true; + continue; + } + if ch == quote { + let literal = input[start_index + 1..index].to_string(); + let consumed = index + ch.len_utf8(); + return Some((literal, consumed)); + } + } + None +} + +fn is_ident_char(ch: char) -> bool { + ch.is_ascii_alphanumeric() || ch == '_' +} + +#[derive(Default)] +struct ScanState { + depth: Depth, + string_delim: Option, + escape: bool, +} + +impl ScanState { + fn observe(&mut self, ch: char) { + if let Some(delim) = self.string_delim { + if self.escape { + self.escape = false; + return; + } + if ch == '\\' { + self.escape = true; + return; + } + if ch == delim { + self.string_delim = None; + } + return; + } + + match ch { + '"' | '\'' => { + self.string_delim = Some(ch); + } + '{' => self.depth.brace += 1, + '}' => self.depth.brace = (self.depth.brace - 1).max(0), + '[' => self.depth.bracket += 1, + ']' => self.depth.bracket = (self.depth.bracket - 1).max(0), + '(' => self.depth.paren += 1, + ')' => self.depth.paren = (self.depth.paren - 1).max(0), + '<' => self.depth.angle += 1, + '>' => { + if self.depth.angle > 0 { + self.depth.angle -= 1; + } + } + _ => {} + } + } + + fn in_string(&self) -> bool { + self.string_delim.is_some() + } +} + +#[derive(Default)] +struct Depth { + brace: i32, + bracket: i32, + paren: i32, + angle: i32, +} + +impl Depth { + fn is_top_level(&self) -> bool { + self.brace == 0 && self.bracket == 0 && self.paren == 0 && self.angle == 0 + } +} + fn build_schema_bundle(schemas: Vec) -> Result { const SPECIAL_DEFINITIONS: &[&str] = &[ "ClientNotification", @@ -740,7 +1393,9 @@ fn generate_index_ts(out_dir: &Path) -> Result { #[cfg(test)] mod tests { use super::*; + use crate::protocol::v2; use anyhow::Result; + use pretty_assertions::assert_eq; use std::collections::BTreeSet; use std::fs; use std::path::PathBuf; @@ -767,9 +1422,34 @@ mod tests { generate_indices: false, ensure_headers: false, run_prettier: false, + experimental_api: false, }; generate_ts_with_options(&output_dir, None, options)?; + let client_request_ts = fs::read_to_string(output_dir.join("ClientRequest.ts"))?; + assert_eq!(client_request_ts.contains("mock/experimentalMethod"), false); + assert_eq!( + client_request_ts.contains("MockExperimentalMethodParams"), + false + ); + let thread_start_ts = + fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.ts"))?; + assert_eq!(thread_start_ts.contains("mockExperimentalField"), false); + assert_eq!( + output_dir + .join("v2") + .join("MockExperimentalMethodParams.ts") + .exists(), + false + ); + assert_eq!( + output_dir + .join("v2") + .join("MockExperimentalMethodResponse.ts") + .exists(), + false + ); + let mut undefined_offenders = Vec::new(); let mut optional_nullable_offenders = BTreeSet::new(); let mut stack = vec![output_dir]; @@ -943,4 +1623,174 @@ mod tests { Ok(()) } + + #[test] + fn generate_ts_with_experimental_api_retains_experimental_entries() -> Result<()> { + let output_dir = + std::env::temp_dir().join(format!("codex_ts_types_experimental_{}", Uuid::now_v7())); + fs::create_dir(&output_dir)?; + + struct TempDirGuard(PathBuf); + + impl Drop for TempDirGuard { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } + + let _guard = TempDirGuard(output_dir.clone()); + + let options = GenerateTsOptions { + generate_indices: false, + ensure_headers: false, + run_prettier: false, + experimental_api: true, + }; + generate_ts_with_options(&output_dir, None, options)?; + + let client_request_ts = fs::read_to_string(output_dir.join("ClientRequest.ts"))?; + assert_eq!(client_request_ts.contains("mock/experimentalMethod"), true); + assert_eq!( + output_dir + .join("v2") + .join("MockExperimentalMethodParams.ts") + .exists(), + true + ); + assert_eq!( + output_dir + .join("v2") + .join("MockExperimentalMethodResponse.ts") + .exists(), + true + ); + + let thread_start_ts = + fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.ts"))?; + assert_eq!(thread_start_ts.contains("mockExperimentalField"), true); + + Ok(()) + } + + #[test] + fn stable_schema_filter_removes_mock_thread_start_field() -> Result<()> { + let output_dir = std::env::temp_dir().join(format!("codex_schema_{}", Uuid::now_v7())); + fs::create_dir(&output_dir)?; + let schema = write_json_schema_with_return::( + &output_dir, + "ThreadStartParams", + )?; + let mut bundle = build_schema_bundle(vec![schema])?; + filter_experimental_schema(&mut bundle)?; + + let definitions = bundle["definitions"] + .as_object() + .expect("schema bundle should include definitions"); + let (_, def_schema) = definitions + .iter() + .find(|(name, _)| definition_matches_type(name, "ThreadStartParams")) + .expect("ThreadStartParams definition should exist"); + let properties = def_schema["properties"] + .as_object() + .expect("ThreadStartParams should have properties"); + assert_eq!(properties.contains_key("mockExperimentalField"), false); + let _cleanup = fs::remove_dir_all(&output_dir); + Ok(()) + } + + #[test] + fn experimental_type_fields_ts_filter_handles_interface_shape() -> Result<()> { + let output_dir = std::env::temp_dir().join(format!("codex_ts_filter_{}", Uuid::now_v7())); + fs::create_dir_all(&output_dir)?; + + struct TempDirGuard(PathBuf); + + impl Drop for TempDirGuard { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } + + let _guard = TempDirGuard(output_dir.clone()); + let path = output_dir.join("CustomParams.ts"); + let content = r#"export interface CustomParams { + stableField: string | null; + unstableField: string | null; + otherStableField: boolean; +} +"#; + fs::write(&path, content)?; + + static CUSTOM_FIELD: crate::experimental_api::ExperimentalField = + crate::experimental_api::ExperimentalField { + type_name: "CustomParams", + field_name: "unstableField", + reason: "custom/unstableField", + }; + filter_experimental_type_fields_ts(&output_dir, &[&CUSTOM_FIELD])?; + + let filtered = fs::read_to_string(&path)?; + assert_eq!(filtered.contains("unstableField"), false); + assert_eq!(filtered.contains("stableField"), true); + assert_eq!(filtered.contains("otherStableField"), true); + Ok(()) + } + + #[test] + fn stable_schema_filter_removes_mock_experimental_method() -> Result<()> { + let output_dir = std::env::temp_dir().join(format!("codex_schema_{}", Uuid::now_v7())); + fs::create_dir(&output_dir)?; + let schema = + write_json_schema_with_return::(&output_dir, "ClientRequest")?; + let mut bundle = build_schema_bundle(vec![schema])?; + filter_experimental_schema(&mut bundle)?; + + let bundle_str = serde_json::to_string(&bundle)?; + assert_eq!(bundle_str.contains("mock/experimentalMethod"), false); + let _cleanup = fs::remove_dir_all(&output_dir); + Ok(()) + } + + #[test] + fn generate_json_filters_experimental_fields_and_methods() -> Result<()> { + let output_dir = std::env::temp_dir().join(format!("codex_schema_{}", Uuid::now_v7())); + fs::create_dir(&output_dir)?; + generate_json_with_experimental(&output_dir, false)?; + + let thread_start_json = + fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.json"))?; + assert_eq!(thread_start_json.contains("mockExperimentalField"), false); + + let client_request_json = fs::read_to_string(output_dir.join("ClientRequest.json"))?; + assert_eq!( + client_request_json.contains("mock/experimentalMethod"), + false + ); + + let bundle_json = + fs::read_to_string(output_dir.join("codex_app_server_protocol.schemas.json"))?; + assert_eq!(bundle_json.contains("mockExperimentalField"), false); + assert_eq!(bundle_json.contains("MockExperimentalMethodParams"), false); + assert_eq!( + bundle_json.contains("MockExperimentalMethodResponse"), + false + ); + assert_eq!( + output_dir + .join("v2") + .join("MockExperimentalMethodParams.json") + .exists(), + false + ); + assert_eq!( + output_dir + .join("v2") + .join("MockExperimentalMethodResponse.json") + .exists(), + false + ); + + let _cleanup = fs::remove_dir_all(&output_dir); + Ok(()) + } } diff --git a/codex-rs/app-server-protocol/src/lib.rs b/codex-rs/app-server-protocol/src/lib.rs index adb96a186e..7b3f17045b 100644 --- a/codex-rs/app-server-protocol/src/lib.rs +++ b/codex-rs/app-server-protocol/src/lib.rs @@ -1,10 +1,15 @@ +mod experimental_api; mod export; mod jsonrpc_lite; mod protocol; mod schema_fixtures; +pub use experimental_api::*; +pub use export::GenerateTsOptions; pub use export::generate_json; +pub use export::generate_json_with_experimental; pub use export::generate_ts; +pub use export::generate_ts_with_options; pub use export::generate_types; pub use jsonrpc_lite::*; pub use protocol::common::*; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index eb59480b58..d20f23a88a 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -41,6 +41,42 @@ pub enum AuthMode { ChatgptAuthTokens, } +macro_rules! experimental_reason_expr { + // If a request variant is explicitly marked experimental, that reason wins. + (#[experimental($reason:expr)] $params:ident $(, $inspect_params:tt)?) => { + Some($reason) + }; + // `inspect_params: true` is used when a method is mostly stable but needs + // field-level gating from its params type (for example, ThreadStart). + ($params:ident, true) => { + crate::experimental_api::ExperimentalApi::experimental_reason($params) + }; + ($params:ident $(, $inspect_params:tt)?) => { + None + }; +} + +macro_rules! experimental_method_entry { + (#[experimental($reason:expr)] => $wire:literal) => { + $wire + }; + (#[experimental($reason:expr)]) => { + $reason + }; + ($($tt:tt)*) => { + "" + }; +} + +macro_rules! experimental_type_entry { + (#[experimental($reason:expr)] $ty:ty) => { + stringify!($ty) + }; + ($ty:ty) => { + "" + }; +} + /// Generates an `enum ClientRequest` where each variant is a request that the /// client can send to the server. Each variant has associated `params` and /// `response` types. Also generates a `export_client_responses()` function to @@ -48,9 +84,11 @@ pub enum AuthMode { macro_rules! client_request_definitions { ( $( - $(#[$variant_meta:meta])* + $(#[experimental($reason:expr)])? + $(#[doc = $variant_doc:literal])* $variant:ident $(=> $wire:literal)? { params: $(#[$params_meta:meta])* $params:ty, + $(inspect_params: $inspect_params:tt,)? response: $response:ty, } ),* $(,)? @@ -60,7 +98,7 @@ macro_rules! client_request_definitions { #[serde(tag = "method", rename_all = "camelCase")] pub enum ClientRequest { $( - $(#[$variant_meta])* + $(#[doc = $variant_doc])* $(#[serde(rename = $wire)] #[ts(rename = $wire)])? $variant { #[serde(rename = "id")] @@ -71,6 +109,38 @@ macro_rules! client_request_definitions { )* } + impl crate::experimental_api::ExperimentalApi for ClientRequest { + fn experimental_reason(&self) -> Option<&'static str> { + match self { + $( + Self::$variant { params: _params, .. } => { + experimental_reason_expr!( + $(#[experimental($reason)])? + _params + $(, $inspect_params)? + ) + } + )* + } + } + } + + pub(crate) const EXPERIMENTAL_CLIENT_METHODS: &[&str] = &[ + $( + experimental_method_entry!($(#[experimental($reason)])? $(=> $wire)?), + )* + ]; + pub(crate) const EXPERIMENTAL_CLIENT_METHOD_PARAM_TYPES: &[&str] = &[ + $( + experimental_type_entry!($(#[experimental($reason)])? $params), + )* + ]; + pub(crate) const EXPERIMENTAL_CLIENT_METHOD_RESPONSE_TYPES: &[&str] = &[ + $( + experimental_type_entry!($(#[experimental($reason)])? $response), + )* + ]; + pub fn export_client_responses( out_dir: &::std::path::Path, ) -> ::std::result::Result<(), ::ts_rs::ExportError> { @@ -112,8 +182,10 @@ client_request_definitions! { /// NEW APIs // Thread lifecycle + // Uses `inspect_params` because only some fields are experimental. ThreadStart => "thread/start" { params: v2::ThreadStartParams, + inspect_params: true, response: v2::ThreadStartResponse, }, ThreadResume => "thread/resume" { @@ -181,11 +253,18 @@ client_request_definitions! { params: v2::ModelListParams, response: v2::ModelListResponse, }, - /// EXPERIMENTAL - list collaboration mode presets. + #[experimental("collaborationMode/list")] + /// Lists collaboration mode presets. CollaborationModeList => "collaborationMode/list" { params: v2::CollaborationModeListParams, response: v2::CollaborationModeListResponse, }, + #[experimental("mock/experimentalMethod")] + /// Test-only method used to validate experimental gating. + MockExperimentalMethod => "mock/experimentalMethod" { + params: v2::MockExperimentalMethodParams, + response: v2::MockExperimentalMethodResponse, + }, McpServerOauthLogin => "mcpServer/oauth/login" { params: v2::McpServerOauthLoginParams, @@ -995,4 +1074,27 @@ mod tests { ); Ok(()) } + + #[test] + fn mock_experimental_method_is_marked_experimental() { + let request = ClientRequest::MockExperimentalMethod { + request_id: RequestId::Integer(1), + params: v2::MockExperimentalMethodParams::default(), + }; + let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request); + assert_eq!(reason, Some("mock/experimentalMethod")); + } + + #[test] + fn thread_start_mock_field_is_marked_experimental() { + let request = ClientRequest::ThreadStart { + request_id: RequestId::Integer(1), + params: v2::ThreadStartParams { + mock_experimental_field: Some("mock".to_string()), + ..Default::default() + }, + }; + let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request); + assert_eq!(reason, Some("thread/start.mockExperimentalField")); + } } diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index 488862b842..09b4130b5d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -33,6 +33,8 @@ use crate::protocol::common::GitSha; #[serde(rename_all = "camelCase")] pub struct InitializeParams { pub client_info: ClientInfo, + #[serde(skip_serializing_if = "Option::is_none")] + pub capabilities: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] @@ -43,6 +45,15 @@ pub struct ClientInfo { pub version: String, } +/// Client-declared capabilities negotiated during initialize. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +pub struct InitializeCapabilities { + /// Opt into receiving experimental API methods and fields. + #[serde(default)] + pub experimental_api: bool, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct InitializeResponse { diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index f99659cd4d..3ca344ee49 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::path::PathBuf; use crate::protocol::common::AuthMode; +use codex_experimental_api_macros::ExperimentalApi; use codex_protocol::account::PlanType; use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment; use codex_protocol::config_types::CollaborationMode; @@ -1165,7 +1166,9 @@ pub struct CommandExecResponse { // === Threads, Turns, and Items === // Thread APIs -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS, ExperimentalApi, +)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartParams { @@ -1179,7 +1182,12 @@ pub struct ThreadStartParams { pub developer_instructions: Option, pub personality: Option, pub ephemeral: Option, + #[experimental("thread/start.dynamicTools")] pub dynamic_tools: Option>, + /// Test-only experimental field used to validate experimental gating and + /// schema filtering behavior in a stable way. + #[experimental("thread/start.mockExperimentalField")] + pub mock_experimental_field: Option, /// If true, opt into emitting raw response items on the event stream. /// /// This is for internal use only (e.g. Codex Cloud). @@ -1188,6 +1196,22 @@ pub struct ThreadStartParams { pub experimental_raw_events: bool, } +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct MockExperimentalMethodParams { + /// Test-only payload field. + pub value: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct MockExperimentalMethodResponse { + /// Echoes the input `value`. + pub echoed: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server-test-client/src/main.rs b/codex-rs/app-server-test-client/src/main.rs index 1e2b94bd8f..d949c3a734 100644 --- a/codex-rs/app-server-test-client/src/main.rs +++ b/codex-rs/app-server-test-client/src/main.rs @@ -28,6 +28,7 @@ use codex_app_server_protocol::FileChangeApprovalDecision; use codex_app_server_protocol::FileChangeRequestApprovalParams; use codex_app_server_protocol::FileChangeRequestApprovalResponse; use codex_app_server_protocol::GetAccountRateLimitsResponse; +use codex_app_server_protocol::InitializeCapabilities; use codex_app_server_protocol::InitializeParams; use codex_app_server_protocol::InitializeResponse; use codex_app_server_protocol::InputItem; @@ -419,6 +420,9 @@ impl CodexClient { title: Some("Codex Toy App Server".to_string()), version: env!("CARGO_PKG_VERSION").to_string(), }, + capabilities: Some(InitializeCapabilities { + experimental_api: true, + }), }, }; diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 1963c0cbdf..67bb36d149 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -15,6 +15,7 @@ - [Skills](#skills) - [Apps](#apps) - [Auth endpoints](#auth-endpoints) +- [Adding an experimental field](#adding-an-experimental-field) ## Protocol @@ -768,3 +769,29 @@ Field notes: - `usedPercent` is current usage within the OpenAI quota window. - `windowDurationMins` is the quota window length. - `resetsAt` is a Unix timestamp (seconds) for the next reset. + +## Adding an experimental field +Use this checklist when introducing a field/method that should only be available when the client opts into experimental APIs. + +At runtime, clients must send `initialize` with `capabilities.experimentalApi = true` to use experimental methods or fields. + +1. Annotate the field in the protocol type (usually `app-server-protocol/src/protocol/v2.rs`) with: + ```rust + #[experimental("thread/start.myField")] + pub my_field: Option, + ``` +2. Ensure the params type derives `ExperimentalApi` so field-level gating can be detected at runtime. + +3. In `app-server-protocol/src/protocol/common.rs`, keep the method stable and use `inspect_params: true` when only some fields are experimental (like `thread/start`). If the entire method is experimental, annotate the method variant with `#[experimental("method/name")]`. + +4. Regenerate protocol fixtures: + + ```bash + just write-app-server-schema + ``` + +5. Verify the protocol crate: + + ```bash + cargo test -p codex-app-server-protocol + ``` diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index d044786bdf..9e462ca431 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -69,6 +69,8 @@ use codex_app_server_protocol::McpServerOauthLoginParams; use codex_app_server_protocol::McpServerOauthLoginResponse; use codex_app_server_protocol::McpServerRefreshResponse; use codex_app_server_protocol::McpServerStatus; +use codex_app_server_protocol::MockExperimentalMethodParams; +use codex_app_server_protocol::MockExperimentalMethodResponse; use codex_app_server_protocol::ModelListParams; use codex_app_server_protocol::ModelListResponse; use codex_app_server_protocol::NewConversationParams; @@ -507,6 +509,9 @@ impl CodexMessageProcessor { .await; }); } + ClientRequest::MockExperimentalMethod { request_id, params } => { + self.mock_experimental_method(request_id, params).await; + } ClientRequest::McpServerOauthLogin { request_id, params } => { self.mcp_server_oauth_login(request_id, params).await; } @@ -1606,6 +1611,7 @@ impl CodexMessageProcessor { base_instructions, developer_instructions, dynamic_tools, + mock_experimental_field: _mock_experimental_field, experimental_raw_events, personality, ephemeral, @@ -3001,6 +3007,16 @@ impl CodexMessageProcessor { outgoing.send_response(request_id, response).await; } + async fn mock_experimental_method( + &self, + request_id: RequestId, + params: MockExperimentalMethodParams, + ) { + let MockExperimentalMethodParams { value } = params; + let response = MockExperimentalMethodResponse { echoed: value }; + self.outgoing.send_response(request_id, response).await; + } + async fn mcp_server_refresh(&self, request_id: RequestId, _params: Option<()>) { let config = match self.load_latest_config().await { Ok(config) => config, diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index b1c899bf1d..db6275dcaf 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -1,5 +1,7 @@ use std::path::PathBuf; use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; use crate::codex_message_processor::CodexMessageProcessor; use crate::codex_message_processor::CodexMessageProcessorArgs; @@ -16,6 +18,7 @@ use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::ConfigWarningNotification; +use codex_app_server_protocol::ExperimentalApi; use codex_app_server_protocol::InitializeResponse; use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCErrorError; @@ -25,6 +28,7 @@ use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequestPayload; +use codex_app_server_protocol::experimental_required_message; use codex_core::AuthManager; use codex_core::ThreadManager; use codex_core::auth::ExternalAuthRefreshContext; @@ -107,6 +111,7 @@ pub(crate) struct MessageProcessor { config_api: ConfigApi, config: Arc, initialized: bool, + experimental_api_enabled: Arc, config_warnings: Vec, } @@ -136,6 +141,7 @@ impl MessageProcessor { config_warnings, } = args; let outgoing = Arc::new(outgoing); + let experimental_api_enabled = Arc::new(AtomicBool::new(false)); let auth_manager = AuthManager::shared( config.codex_home.clone(), false, @@ -173,6 +179,7 @@ impl MessageProcessor { config_api, config, initialized: false, + experimental_api_enabled, config_warnings, } } @@ -218,6 +225,12 @@ impl MessageProcessor { self.outgoing.send_error(request_id, error).await; return; } else { + let experimental_api_enabled = params + .capabilities + .as_ref() + .is_some_and(|cap| cap.experimental_api); + self.experimental_api_enabled + .store(experimental_api_enabled, Ordering::Relaxed); let ClientInfo { name, title: _title, @@ -281,6 +294,18 @@ impl MessageProcessor { } } + if let Some(reason) = codex_request.experimental_reason() + && !self.experimental_api_enabled.load(Ordering::Relaxed) + { + let error = JSONRPCErrorError { + code: INVALID_REQUEST_ERROR_CODE, + message: experimental_required_message(reason), + data: None, + }; + self.outgoing.send_error(request_id, error).await; + return; + } + match codex_request { ClientRequest::ConfigRead { request_id, params } => { self.handle_config_read(request_id, params).await; diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 90cd7635a3..4804a8cd37 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -26,6 +26,7 @@ use codex_app_server_protocol::FeedbackUploadParams; use codex_app_server_protocol::ForkConversationParams; use codex_app_server_protocol::GetAccountParams; use codex_app_server_protocol::GetAuthStatusParams; +use codex_app_server_protocol::InitializeCapabilities; use codex_app_server_protocol::InitializeParams; use codex_app_server_protocol::InterruptConversationParams; use codex_app_server_protocol::JSONRPCError; @@ -37,6 +38,7 @@ use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::ListConversationsParams; use codex_app_server_protocol::LoginAccountParams; use codex_app_server_protocol::LoginApiKeyParams; +use codex_app_server_protocol::MockExperimentalMethodParams; use codex_app_server_protocol::ModelListParams; use codex_app_server_protocol::NewConversationParams; use codex_app_server_protocol::RemoveConversationListenerParams; @@ -164,7 +166,32 @@ impl McpProcess { &mut self, client_info: ClientInfo, ) -> anyhow::Result { - let params = Some(serde_json::to_value(InitializeParams { client_info })?); + self.initialize_with_capabilities( + client_info, + Some(InitializeCapabilities { + experimental_api: true, + }), + ) + .await + } + + pub async fn initialize_with_capabilities( + &mut self, + client_info: ClientInfo, + capabilities: Option, + ) -> anyhow::Result { + self.initialize_with_params(InitializeParams { + client_info, + capabilities, + }) + .await + } + + async fn initialize_with_params( + &mut self, + params: InitializeParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); let request_id = self.send_request("initialize", params).await?; let message = self.read_jsonrpc_message().await?; match message { @@ -451,6 +478,15 @@ impl McpProcess { self.send_request("collaborationMode/list", params).await } + /// Send a `mock/experimentalMethod` JSON-RPC request. + pub async fn send_mock_experimental_method_request( + &mut self, + params: MockExperimentalMethodParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("mock/experimentalMethod", params).await + } + /// Send a `resumeConversation` JSON-RPC request. pub async fn send_resume_conversation_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/v2/experimental_api.rs b/codex-rs/app-server/tests/suite/v2/experimental_api.rs new file mode 100644 index 0000000000..5116633a48 --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/experimental_api.rs @@ -0,0 +1,160 @@ +use anyhow::Result; +use app_test_support::DEFAULT_CLIENT_NAME; +use app_test_support::McpProcess; +use app_test_support::create_mock_responses_server_sequence_unchecked; +use app_test_support::to_response; +use codex_app_server_protocol::ClientInfo; +use codex_app_server_protocol::InitializeCapabilities; +use codex_app_server_protocol::JSONRPCError; +use codex_app_server_protocol::JSONRPCMessage; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::MockExperimentalMethodParams; +use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ThreadStartParams; +use codex_app_server_protocol::ThreadStartResponse; +use pretty_assertions::assert_eq; +use std::path::Path; +use std::time::Duration; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); + +#[tokio::test] +async fn mock_experimental_method_requires_experimental_api_capability() -> Result<()> { + let codex_home = TempDir::new()?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + + let init = mcp + .initialize_with_capabilities( + default_client_info(), + Some(InitializeCapabilities { + experimental_api: false, + }), + ) + .await?; + let JSONRPCMessage::Response(_) = init else { + anyhow::bail!("expected initialize response, got {init:?}"); + }; + + let request_id = mcp + .send_mock_experimental_method_request(MockExperimentalMethodParams::default()) + .await?; + let error = timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(request_id)), + ) + .await??; + assert_experimental_capability_error(error, "mock/experimentalMethod"); + Ok(()) +} + +#[tokio::test] +async fn thread_start_mock_field_requires_experimental_api_capability() -> Result<()> { + let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + let init = mcp + .initialize_with_capabilities( + default_client_info(), + Some(InitializeCapabilities { + experimental_api: false, + }), + ) + .await?; + let JSONRPCMessage::Response(_) = init else { + anyhow::bail!("expected initialize response, got {init:?}"); + }; + + let request_id = mcp + .send_thread_start_request(ThreadStartParams { + mock_experimental_field: Some("mock".to_string()), + ..Default::default() + }) + .await?; + + let error = timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(request_id)), + ) + .await??; + assert_experimental_capability_error(error, "thread/start.mockExperimentalField"); + Ok(()) +} + +#[tokio::test] +async fn thread_start_without_dynamic_tools_allows_without_experimental_api_capability() +-> Result<()> { + let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + let init = mcp + .initialize_with_capabilities( + default_client_info(), + Some(InitializeCapabilities { + experimental_api: false, + }), + ) + .await?; + let JSONRPCMessage::Response(_) = init else { + anyhow::bail!("expected initialize response, got {init:?}"); + }; + + let request_id = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let response: JSONRPCResponse = timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let _: ThreadStartResponse = to_response(response)?; + Ok(()) +} + +fn default_client_info() -> ClientInfo { + ClientInfo { + name: DEFAULT_CLIENT_NAME.to_string(), + title: None, + version: "0.1.0".to_string(), + } +} + +fn assert_experimental_capability_error(error: JSONRPCError, reason: &str) { + assert_eq!(error.error.code, -32600); + assert_eq!( + error.error.message, + format!("{reason} requires experimentalApi capability") + ); + assert_eq!(error.error.data, None); +} + +fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { + let config_toml = codex_home.join("config.toml"); + std::fs::write( + config_toml, + format!( + r#" +model = "mock-model" +approval_policy = "never" +sandbox_mode = "read-only" + +model_provider = "mock_provider" + +[model_providers.mock_provider] +name = "Mock provider for test" +base_url = "{server_uri}/v1" +wire_api = "responses" +request_max_retries = 0 +stream_max_retries = 0 +"# + ), + ) +} diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index b67c3e5c33..3a84e48b9e 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -5,6 +5,7 @@ mod collaboration_mode_list; mod compaction; mod config_rpc; mod dynamic_tools; +mod experimental_api; mod initialize; mod model_list; mod output_schema; diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 7ab8bc937a..01aef19176 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -303,6 +303,10 @@ struct GenerateTsCommand { /// Optional path to the Prettier executable to format generated files #[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")] prettier: Option, + + /// Include experimental methods and fields in the generated output + #[arg(long = "experimental", default_value_t = false)] + experimental: bool, } #[derive(Debug, Args)] @@ -310,6 +314,10 @@ struct GenerateJsonSchemaCommand { /// Output directory where the schema bundle will be written #[arg(short = 'o', long = "out", value_name = "DIR")] out_dir: PathBuf, + + /// Include experimental methods and fields in the generated output + #[arg(long = "experimental", default_value_t = false)] + experimental: bool, } #[derive(Debug, Parser)] @@ -539,13 +547,21 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() .await?; } Some(AppServerSubcommand::GenerateTs(gen_cli)) => { - codex_app_server_protocol::generate_ts( + let options = codex_app_server_protocol::GenerateTsOptions { + experimental_api: gen_cli.experimental, + ..Default::default() + }; + codex_app_server_protocol::generate_ts_with_options( &gen_cli.out_dir, gen_cli.prettier.as_deref(), + options, )?; } Some(AppServerSubcommand::GenerateJsonSchema(gen_cli)) => { - codex_app_server_protocol::generate_json(&gen_cli.out_dir)?; + codex_app_server_protocol::generate_json_with_experimental( + &gen_cli.out_dir, + gen_cli.experimental, + )?; } }, Some(Subcommand::Resume(ResumeCommand { diff --git a/codex-rs/codex-experimental-api-macros/BUILD.bazel b/codex-rs/codex-experimental-api-macros/BUILD.bazel new file mode 100644 index 0000000000..370a4ed8c5 --- /dev/null +++ b/codex-rs/codex-experimental-api-macros/BUILD.bazel @@ -0,0 +1,7 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "codex-experimental-api-macros", + crate_name = "codex_experimental_api_macros", + proc_macro = True, +) diff --git a/codex-rs/codex-experimental-api-macros/Cargo.toml b/codex-rs/codex-experimental-api-macros/Cargo.toml new file mode 100644 index 0000000000..cef1ec243f --- /dev/null +++ b/codex-rs/codex-experimental-api-macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "codex-experimental-api-macros" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full", "extra-traits"] } + +[lints] +workspace = true diff --git a/codex-rs/codex-experimental-api-macros/src/lib.rs b/codex-rs/codex-experimental-api-macros/src/lib.rs new file mode 100644 index 0000000000..6262be3869 --- /dev/null +++ b/codex-rs/codex-experimental-api-macros/src/lib.rs @@ -0,0 +1,293 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::Attribute; +use syn::Data; +use syn::DataEnum; +use syn::DataStruct; +use syn::DeriveInput; +use syn::Field; +use syn::Fields; +use syn::Ident; +use syn::LitStr; +use syn::Type; +use syn::parse_macro_input; + +#[proc_macro_derive(ExperimentalApi, attributes(experimental))] +pub fn derive_experimental_api(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match &input.data { + Data::Struct(data) => derive_for_struct(&input, data), + Data::Enum(data) => derive_for_enum(&input, data), + Data::Union(_) => { + syn::Error::new_spanned(&input.ident, "ExperimentalApi does not support unions") + .to_compile_error() + .into() + } + } +} + +fn derive_for_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream { + let name = &input.ident; + let type_name_lit = LitStr::new(&name.to_string(), Span::call_site()); + + let (checks, experimental_fields, registrations) = match &data.fields { + Fields::Named(named) => { + let mut checks = Vec::new(); + let mut experimental_fields = Vec::new(); + let mut registrations = Vec::new(); + for field in &named.named { + let reason = experimental_reason(&field.attrs); + if let Some(reason) = reason { + let expr = experimental_presence_expr(field, false); + checks.push(quote! { + if #expr { + return Some(#reason); + } + }); + + if let Some(field_name) = field_serialized_name(field) { + let field_name_lit = LitStr::new(&field_name, Span::call_site()); + experimental_fields.push(quote! { + crate::experimental_api::ExperimentalField { + type_name: #type_name_lit, + field_name: #field_name_lit, + reason: #reason, + } + }); + registrations.push(quote! { + ::inventory::submit! { + crate::experimental_api::ExperimentalField { + type_name: #type_name_lit, + field_name: #field_name_lit, + reason: #reason, + } + } + }); + } + } + } + (checks, experimental_fields, registrations) + } + Fields::Unnamed(unnamed) => { + let mut checks = Vec::new(); + let mut experimental_fields = Vec::new(); + let mut registrations = Vec::new(); + for (index, field) in unnamed.unnamed.iter().enumerate() { + let reason = experimental_reason(&field.attrs); + if let Some(reason) = reason { + let expr = index_presence_expr(index, &field.ty); + checks.push(quote! { + if #expr { + return Some(#reason); + } + }); + + let field_name_lit = LitStr::new(&index.to_string(), Span::call_site()); + experimental_fields.push(quote! { + crate::experimental_api::ExperimentalField { + type_name: #type_name_lit, + field_name: #field_name_lit, + reason: #reason, + } + }); + registrations.push(quote! { + ::inventory::submit! { + crate::experimental_api::ExperimentalField { + type_name: #type_name_lit, + field_name: #field_name_lit, + reason: #reason, + } + } + }); + } + } + (checks, experimental_fields, registrations) + } + Fields::Unit => (Vec::new(), Vec::new(), Vec::new()), + }; + + let checks = if checks.is_empty() { + quote! { None } + } else { + quote! { + #(#checks)* + None + } + }; + + let experimental_fields = if experimental_fields.is_empty() { + quote! { &[] } + } else { + quote! { &[ #(#experimental_fields,)* ] } + }; + + let expanded = quote! { + #(#registrations)* + + impl #name { + pub(crate) const EXPERIMENTAL_FIELDS: &'static [crate::experimental_api::ExperimentalField] = + #experimental_fields; + } + + impl crate::experimental_api::ExperimentalApi for #name { + fn experimental_reason(&self) -> Option<&'static str> { + #checks + } + } + }; + expanded.into() +} + +fn derive_for_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream { + let name = &input.ident; + let mut match_arms = Vec::new(); + + for variant in &data.variants { + let variant_name = &variant.ident; + let pattern = match &variant.fields { + Fields::Named(_) => quote!(Self::#variant_name { .. }), + Fields::Unnamed(_) => quote!(Self::#variant_name ( .. )), + Fields::Unit => quote!(Self::#variant_name), + }; + let reason = experimental_reason(&variant.attrs); + if let Some(reason) = reason { + match_arms.push(quote! { + #pattern => Some(#reason), + }); + } else { + match_arms.push(quote! { + #pattern => None, + }); + } + } + + let expanded = quote! { + impl crate::experimental_api::ExperimentalApi for #name { + fn experimental_reason(&self) -> Option<&'static str> { + match self { + #(#match_arms)* + } + } + } + }; + expanded.into() +} + +fn experimental_reason(attrs: &[Attribute]) -> Option { + let attr = attrs + .iter() + .find(|attr| attr.path().is_ident("experimental"))?; + attr.parse_args::().ok() +} + +fn field_serialized_name(field: &Field) -> Option { + let ident = field.ident.as_ref()?; + let name = ident.to_string(); + Some(snake_to_camel(&name)) +} + +fn snake_to_camel(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + let mut upper = false; + for ch in s.chars() { + if ch == '_' { + upper = true; + continue; + } + if upper { + out.push(ch.to_ascii_uppercase()); + upper = false; + } else { + out.push(ch); + } + } + out +} + +fn experimental_presence_expr( + field: &Field, + tuple_struct: bool, +) -> Option { + if tuple_struct { + return None; + } + let ident = field.ident.as_ref()?; + Some(presence_expr_for_access(quote!(self.#ident), &field.ty)) +} + +fn index_presence_expr(index: usize, ty: &Type) -> proc_macro2::TokenStream { + let index = syn::Index::from(index); + presence_expr_for_access(quote!(self.#index), ty) +} + +fn presence_expr_for_access( + access: proc_macro2::TokenStream, + ty: &Type, +) -> proc_macro2::TokenStream { + if let Some(inner) = option_inner(ty) { + let inner_expr = presence_expr_for_ref(quote!(value), inner); + return quote! { + #access.as_ref().is_some_and(|value| #inner_expr) + }; + } + if is_vec_like(ty) || is_map_like(ty) { + return quote! { !#access.is_empty() }; + } + if is_bool(ty) { + return quote! { #access }; + } + quote! { true } +} + +fn presence_expr_for_ref(access: proc_macro2::TokenStream, ty: &Type) -> proc_macro2::TokenStream { + if let Some(inner) = option_inner(ty) { + let inner_expr = presence_expr_for_ref(quote!(value), inner); + return quote! { + #access.as_ref().is_some_and(|value| #inner_expr) + }; + } + if is_vec_like(ty) || is_map_like(ty) { + return quote! { !#access.is_empty() }; + } + if is_bool(ty) { + return quote! { *#access }; + } + quote! { true } +} + +fn option_inner(ty: &Type) -> Option<&Type> { + let Type::Path(type_path) = ty else { + return None; + }; + let segment = type_path.path.segments.last()?; + if segment.ident != "Option" { + return None; + } + let syn::PathArguments::AngleBracketed(args) = &segment.arguments else { + return None; + }; + args.args.iter().find_map(|arg| match arg { + syn::GenericArgument::Type(inner) => Some(inner), + _ => None, + }) +} + +fn is_vec_like(ty: &Type) -> bool { + type_last_ident(ty).is_some_and(|ident| ident == "Vec") +} + +fn is_map_like(ty: &Type) -> bool { + type_last_ident(ty).is_some_and(|ident| ident == "HashMap" || ident == "BTreeMap") +} + +fn is_bool(ty: &Type) -> bool { + type_last_ident(ty).is_some_and(|ident| ident == "bool") +} + +fn type_last_ident(ty: &Type) -> Option { + let Type::Path(type_path) = ty else { + return None; + }; + type_path.path.segments.last().map(|seg| seg.ident.clone()) +} diff --git a/codex-rs/debug-client/src/client.rs b/codex-rs/debug-client/src/client.rs index b5bac6fb58..cf54ef9855 100644 --- a/codex-rs/debug-client/src/client.rs +++ b/codex-rs/debug-client/src/client.rs @@ -20,6 +20,7 @@ use codex_app_server_protocol::ClientNotification; use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::CommandExecutionApprovalDecision; use codex_app_server_protocol::FileChangeApprovalDecision; +use codex_app_server_protocol::InitializeCapabilities; use codex_app_server_protocol::JSONRPCMessage; use codex_app_server_protocol::JSONRPCRequest; use codex_app_server_protocol::JSONRPCResponse; @@ -99,6 +100,9 @@ impl AppServerClient { title: Some("Debug Client".to_string()), version: env!("CARGO_PKG_VERSION").to_string(), }, + capabilities: Some(InitializeCapabilities { + experimental_api: true, + }), }, }; diff --git a/codex-rs/utils/cargo-bin/src/lib.rs b/codex-rs/utils/cargo-bin/src/lib.rs index 84f69af358..6517a77c94 100644 --- a/codex-rs/utils/cargo-bin/src/lib.rs +++ b/codex-rs/utils/cargo-bin/src/lib.rs @@ -35,6 +35,7 @@ pub enum CargoBinError { /// In `cargo test`, `CARGO_BIN_EXE_*` env vars are absolute. /// In `bazel test`, `CARGO_BIN_EXE_*` env vars are rlocationpaths, intended to be consumed by `rlocation`. /// This helper allows callers to transparently support both. +#[allow(deprecated)] pub fn cargo_bin(name: &str) -> Result { let env_keys = cargo_bin_env_keys(name); for key in &env_keys { diff --git a/defs.bzl b/defs.bzl index 40112969c8..5800d996aa 100644 --- a/defs.bzl +++ b/defs.bzl @@ -1,7 +1,7 @@ load("@crates//:data.bzl", "DEP_DATA") load("@crates//:defs.bzl", "all_crate_deps") load("@rules_platform//platform_data:defs.bzl", "platform_data") -load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_macro", "rust_test") load("@rules_rust//cargo/private:cargo_build_script_wrapper.bzl", "cargo_build_script") PLATFORMS = [ @@ -34,6 +34,7 @@ def codex_rust_crate( crate_features = [], crate_srcs = None, crate_edition = None, + proc_macro = False, build_script_data = [], compile_data = [], lib_data_extra = [], @@ -63,6 +64,7 @@ def codex_rust_crate( crate_srcs: Optional explicit srcs; defaults to `src/**/*.rs`. crate_edition: Rust edition override, if not default. You probably don't want this, it's only here for a single caller. + proc_macro: Whether this crate builds a proc-macro library. build_script_data: Data files exposed to the build script at runtime. compile_data: Non-Rust compile-time data for the library target. lib_data_extra: Extra runtime data for the library target. @@ -109,7 +111,8 @@ def codex_rust_crate( deps = deps + [name + "-build-script"] if lib_srcs: - rust_library( + lib_rule = rust_proc_macro if proc_macro else rust_library + lib_rule( name = name, crate_name = crate_name, crate_features = crate_features, From 4971e96a98cc2a3ef54913eb2e2f0ef6d30cf9ff Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 2 Feb 2026 13:52:45 +0100 Subject: [PATCH 066/139] nit: shell snapshot retention to 3 days (#10382) --- codex-rs/core/src/shell_snapshot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/src/shell_snapshot.rs b/codex-rs/core/src/shell_snapshot.rs index 7ba1b67caf..1277328244 100644 --- a/codex-rs/core/src/shell_snapshot.rs +++ b/codex-rs/core/src/shell_snapshot.rs @@ -29,7 +29,7 @@ pub struct ShellSnapshot { } const SNAPSHOT_TIMEOUT: Duration = Duration::from_secs(10); -const SNAPSHOT_RETENTION: Duration = Duration::from_secs(60 * 60 * 24 * 7); // 7 days retention. +const SNAPSHOT_RETENTION: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days retention. const SNAPSHOT_DIR: &str = "shell_snapshots"; const EXCLUDED_EXPORT_VARS: &[&str] = &["PWD", "OLDPWD"]; From e9a774e7aebf4d8d9bbf3a4dc557c4ca8835aa20 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 2 Feb 2026 13:52:49 +0100 Subject: [PATCH 067/139] fix: thread listing (#10383) --- codex-rs/core/src/rollout/list.rs | 5 +++++ codex-rs/core/src/state_db.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 18e862c7f0..a66986724a 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -983,6 +983,11 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result Date: Mon, 2 Feb 2026 14:33:28 +0100 Subject: [PATCH 068/139] fix: Rfc3339 casting (#10386) --- codex-rs/core/src/rollout/list.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index a66986724a..f42b4795cd 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -243,9 +243,7 @@ impl serde::Serialize for Cursor { { let ts_str = self .ts - .format(&format_description!( - "[year]-[month]-[day]T[hour]-[minute]-[second]" - )) + .format(&Rfc3339) .map_err(|e| serde::ser::Error::custom(format!("format error: {e}")))?; serializer.serialize_str(&format!("{ts_str}|{}", self.id)) } @@ -628,9 +626,13 @@ pub fn parse_cursor(token: &str) -> Option { return None; }; - let format: &[FormatItem] = - format_description!("[year]-[month]-[day]T[hour]-[minute]-[second]"); - let ts = PrimitiveDateTime::parse(file_ts, format).ok()?.assume_utc(); + let ts = OffsetDateTime::parse(file_ts, &Rfc3339).ok().or_else(|| { + let format: &[FormatItem] = + format_description!("[year]-[month]-[day]T[hour]-[minute]-[second]"); + PrimitiveDateTime::parse(file_ts, format) + .ok() + .map(PrimitiveDateTime::assume_utc) + })?; Some(Cursor::new(ts, uuid)) } @@ -967,10 +969,7 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result { summary.source = Some(session_meta_line.meta.source.clone()); summary.model_provider = session_meta_line.meta.model_provider.clone(); - summary.created_at = summary - .created_at - .clone() - .or_else(|| Some(rollout_line.timestamp.clone())); + summary.created_at = Some(session_meta_line.meta.timestamp.clone()); summary.saw_session_meta = true; if summary.head.len() < head_limit && let Ok(val) = serde_json::to_value(session_meta_line) From d1e71cd20235b0040666c3e6a8f5dce2903972c3 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Mon, 2 Feb 2026 08:41:02 -0800 Subject: [PATCH 069/139] feat: add MCP protocol types and rmcp adapters (#10356) Currently, types from our custom `mcp-types` crate are part of some of our APIs: https://github.com/openai/codex/blob/03fcd12e77fedf4fa327af27e2e476e1ebc5f651/codex-rs/app-server-protocol/src/protocol/v2.rs#L43-L46 To eliminate this crate in #10349 by switching to `rmcp`, we need our own wrappers for the `rmcp` types that we can use in our API, which is what this PR does. Note this PR introduces the new API types, but we do not make use of them until #10349. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10356). * #10357 * #10349 * __->__ #10356 --- codex-rs/protocol/src/lib.rs | 1 + codex-rs/protocol/src/mcp.rs | 324 +++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 codex-rs/protocol/src/mcp.rs diff --git a/codex-rs/protocol/src/lib.rs b/codex-rs/protocol/src/lib.rs index 60b01bbd73..5841b1187e 100644 --- a/codex-rs/protocol/src/lib.rs +++ b/codex-rs/protocol/src/lib.rs @@ -6,6 +6,7 @@ pub mod config_types; pub mod custom_prompts; pub mod dynamic_tools; pub mod items; +pub mod mcp; pub mod message_history; pub mod models; pub mod num_format; diff --git a/codex-rs/protocol/src/mcp.rs b/codex-rs/protocol/src/mcp.rs new file mode 100644 index 0000000000..88a1f989c1 --- /dev/null +++ b/codex-rs/protocol/src/mcp.rs @@ -0,0 +1,324 @@ +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use ts_rs::TS; + +/// Types used when representing Model Context Protocol (MCP) values inside the +/// Codex protocol. +/// +/// We intentionally keep these types TS/JSON-schema friendly (via `ts-rs` and +/// `schemars`) so they can be embedded in Codex's own protocol structures. + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)] +#[serde(untagged)] +pub enum RequestId { + String(String), + #[ts(type = "number")] + Integer(i64), +} + +impl std::fmt::Display for RequestId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RequestId::String(s) => f.write_str(s), + RequestId::Integer(i) => i.fmt(f), + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +pub struct Tool { + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub title: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub description: Option, + pub input_schema: serde_json::Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub output_schema: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub annotations: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub icons: Option>, + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub meta: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +pub struct Resource { + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub annotations: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub mime_type: Option, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + #[ts(type = "number")] + pub size: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub title: Option, + pub uri: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub icons: Option>, + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub meta: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +pub struct ResourceTemplate { + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub annotations: Option, + pub uri_template: String, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub title: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub mime_type: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +pub struct CallToolResult { + pub content: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub structured_content: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub is_error: Option, + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub meta: Option, +} + +// === Adapter helpers === +// +// These types and conversions intentionally live in `codex-protocol` so other crates can convert +// “wire-shaped” MCP JSON (typically coming from rmcp model structs serialized with serde) into our +// TS/JsonSchema-friendly protocol types without depending on `mcp-types`. + +fn deserialize_lossy_opt_i64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + match Option::::deserialize(deserializer)? { + Some(number) => { + if let Some(v) = number.as_i64() { + Ok(Some(v)) + } else if let Some(v) = number.as_u64() { + Ok(i64::try_from(v).ok()) + } else { + Ok(None) + } + } + None => Ok(None), + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ToolSerde { + name: String, + #[serde(default)] + title: Option, + #[serde(default)] + description: Option, + #[serde(default, rename = "inputSchema", alias = "input_schema")] + input_schema: serde_json::Value, + #[serde(default, rename = "outputSchema", alias = "output_schema")] + output_schema: Option, + #[serde(default)] + annotations: Option, + #[serde(default)] + icons: Option>, + #[serde(rename = "_meta", default)] + meta: Option, +} + +impl From for Tool { + fn from(value: ToolSerde) -> Self { + let ToolSerde { + name, + title, + description, + input_schema, + output_schema, + annotations, + icons, + meta, + } = value; + Self { + name, + title, + description, + input_schema, + output_schema, + annotations, + icons, + meta, + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ResourceSerde { + #[serde(default)] + annotations: Option, + #[serde(default)] + description: Option, + #[serde(rename = "mimeType", alias = "mime_type", default)] + mime_type: Option, + name: String, + #[serde(default, deserialize_with = "deserialize_lossy_opt_i64")] + size: Option, + #[serde(default)] + title: Option, + uri: String, + #[serde(default)] + icons: Option>, + #[serde(rename = "_meta", default)] + meta: Option, +} + +impl From for Resource { + fn from(value: ResourceSerde) -> Self { + let ResourceSerde { + annotations, + description, + mime_type, + name, + size, + title, + uri, + icons, + meta, + } = value; + Self { + annotations, + description, + mime_type, + name, + size, + title, + uri, + icons, + meta, + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ResourceTemplateSerde { + #[serde(default)] + annotations: Option, + #[serde(rename = "uriTemplate", alias = "uri_template")] + uri_template: String, + name: String, + #[serde(default)] + title: Option, + #[serde(default)] + description: Option, + #[serde(rename = "mimeType", alias = "mime_type", default)] + mime_type: Option, +} + +impl From for ResourceTemplate { + fn from(value: ResourceTemplateSerde) -> Self { + let ResourceTemplateSerde { + annotations, + uri_template, + name, + title, + description, + mime_type, + } = value; + Self { + annotations, + uri_template, + name, + title, + description, + mime_type, + } + } +} + +impl Tool { + pub fn from_mcp_value(value: serde_json::Value) -> Result { + Ok(serde_json::from_value::(value)?.into()) + } +} + +impl Resource { + pub fn from_mcp_value(value: serde_json::Value) -> Result { + Ok(serde_json::from_value::(value)?.into()) + } +} + +impl ResourceTemplate { + pub fn from_mcp_value(value: serde_json::Value) -> Result { + Ok(serde_json::from_value::(value)?.into()) + } +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn resource_size_deserializes_without_narrowing() { + let resource = serde_json::json!({ + "name": "big", + "uri": "file:///tmp/big", + "size": 5_000_000_000u64, + }); + + let parsed = Resource::from_mcp_value(resource).expect("should deserialize"); + assert_eq!(parsed.size, Some(5_000_000_000)); + + let resource = serde_json::json!({ + "name": "negative", + "uri": "file:///tmp/negative", + "size": -1, + }); + + let parsed = Resource::from_mcp_value(resource).expect("should deserialize"); + assert_eq!(parsed.size, Some(-1)); + + let resource = serde_json::json!({ + "name": "too_big_for_i64", + "uri": "file:///tmp/too_big_for_i64", + "size": 18446744073709551615u64, + }); + + let parsed = Resource::from_mcp_value(resource).expect("should deserialize"); + assert_eq!(parsed.size, None); + } +} From 3392c5af243821c85c3b816cbb0623a795a91385 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Mon, 2 Feb 2026 09:53:29 -0800 Subject: [PATCH 070/139] Nicer highlighting of slash commands, /plan accepts prompt args and pasted images (#10269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Make typed slash commands become text elements when the user hits space, including paste‑burst spaces. - Enable `/plan` to accept inline args and submit them in plan mode, mirroring `/review` behavior and blocking submission while a task is running. - Preserve text elements/attachments for slash commands that take args. image ## Changes - Add safe helper to insert element ranges in the textarea. - Extend command‑with‑args pipeline to carry text elements and reuse submission prep. - Update `/plan` dispatch to switch to plan mode then submit prompt + elements. - Document new composer behavior and add tests. ## Notes - `/plan` is blocked during active tasks (same as `/review`). - Slash‑command elementization recognizes built‑ins and `/prompts:` custom commands only. ## Codex author `codex fork 019c16d3-4520-7bb0-9b9d-48720d40a8ab` --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 381 ++++++++++++++++-- codex-rs/tui/src/bottom_pane/mod.rs | 14 + codex-rs/tui/src/bottom_pane/textarea.rs | 40 ++ codex-rs/tui/src/chatwidget.rs | 59 ++- codex-rs/tui/src/chatwidget/tests.rs | 44 ++ codex-rs/tui/src/slash_command.rs | 10 +- docs/tui-chat-composer.md | 5 + 7 files changed, 517 insertions(+), 36 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 979213112f..068674b418 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -4,6 +4,7 @@ //! //! - Editing the input buffer (a [`TextArea`]), including placeholder "elements" for attachments. //! - Routing keys to the active popup (slash commands, file search, skill/apps mentions). +//! - Promoting typed slash commands into atomic elements when the command name is completed. //! - Handling submit vs newline on Enter. //! - Turning raw key streams into explicit paste operations on platforms where terminals //! don't provide reliable bracketed paste (notably Windows). @@ -36,6 +37,8 @@ //! //! The numeric auto-submit path used by the slash popup performs the same pending-paste expansion //! and attachment pruning, and clears pending paste state on success. +//! Slash commands with arguments (like `/plan` and `/review`) reuse the same preparation path so +//! pasted content and text elements are preserved when extracting args. //! //! # Non-bracketed Paste Bursts //! @@ -164,6 +167,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; +use std::ops::Range; use std::path::PathBuf; use std::time::Duration; use std::time::Instant; @@ -184,7 +188,7 @@ pub enum InputResult { text_elements: Vec, }, Command(SlashCommand), - CommandWithArgs(SlashCommand, String), + CommandWithArgs(SlashCommand, String, Vec), None, } @@ -747,6 +751,7 @@ impl ChatComposer { /// Move the cursor to the end of the current text buffer. pub(crate) fn move_cursor_to_end(&mut self) { self.textarea.set_cursor(self.textarea.text().len()); + self.sync_popups(); } pub(crate) fn clear_for_ctrl_c(&mut self) -> Option { @@ -1235,6 +1240,7 @@ impl ChatComposer { self.handle_paste(pasted); } self.textarea.input(input); + let text_after = self.textarea.text(); self.pending_pastes .retain(|(placeholder, _)| text_after.contains(placeholder)); @@ -1798,7 +1804,12 @@ impl ChatComposer { /// Prepare text for submission/queuing. Returns None if submission should be suppressed. /// On success, clears pending paste payloads because placeholders have been expanded. - fn prepare_submission_text(&mut self) -> Option<(String, Vec)> { + /// + /// When `record_history` is true, the final submission is stored for ↑/↓ recall. + fn prepare_submission_text( + &mut self, + record_history: bool, + ) -> Option<(String, Vec)> { let mut text = self.textarea.text().to_string(); let original_input = text.clone(); let original_text_elements = self.textarea.text_elements(); @@ -1896,7 +1907,7 @@ impl ChatComposer { if text.is_empty() && self.attached_images.is_empty() { return None; } - if !text.is_empty() || !self.attached_images.is_empty() { + if record_history && (!text.is_empty() || !self.attached_images.is_empty()) { let local_image_paths = self .attached_images .iter() @@ -1978,7 +1989,7 @@ impl ChatComposer { return (result, true); } - if let Some((text, text_elements)) = self.prepare_submission_text() { + if let Some((text, text_elements)) = self.prepare_submission_text(true) { if should_queue { ( InputResult::Queued { @@ -2026,6 +2037,9 @@ impl ChatComposer { self.windows_degraded_sandbox_active, ) { + if self.reject_slash_command_if_unavailable(cmd) { + return Some(InputResult::None); + } self.textarea.set_text_clearing_elements(""); Some(InputResult::Command(cmd)) } else { @@ -2039,28 +2053,104 @@ impl ChatComposer { if !self.slash_commands_enabled() { return None; } - let original_input = self.textarea.text().to_string(); - let input_starts_with_space = original_input.starts_with(' '); - - if !input_starts_with_space { - let text = self.textarea.text().to_string(); - if let Some((name, rest, _rest_offset)) = parse_slash_name(&text) - && !rest.is_empty() - && !name.contains('/') - && let Some(cmd) = slash_commands::find_builtin_command( - name, - self.collaboration_modes_enabled, - self.connectors_enabled, - self.personality_command_enabled, - self.windows_degraded_sandbox_active, - ) - && matches!(cmd, SlashCommand::Review | SlashCommand::Rename) - { - self.textarea.set_text_clearing_elements(""); - return Some(InputResult::CommandWithArgs(cmd, rest.to_string())); - } + let text = self.textarea.text().to_string(); + if text.starts_with(' ') { + return None; } - None + + let (name, rest, rest_offset) = parse_slash_name(&text)?; + if rest.is_empty() || name.contains('/') { + return None; + } + + let cmd = slash_commands::find_builtin_command( + name, + self.collaboration_modes_enabled, + self.connectors_enabled, + self.personality_command_enabled, + self.windows_degraded_sandbox_active, + )?; + + if !cmd.supports_inline_args() { + return None; + } + if self.reject_slash_command_if_unavailable(cmd) { + return Some(InputResult::None); + } + + let mut args_elements = + Self::slash_command_args_elements(rest, rest_offset, &self.textarea.text_elements()); + let trimmed_rest = rest.trim(); + args_elements = Self::trim_text_elements(rest, trimmed_rest, args_elements); + Some(InputResult::CommandWithArgs( + cmd, + trimmed_rest.to_string(), + args_elements, + )) + } + + /// Expand pending placeholders and extract normalized inline-command args. + /// + /// Inline-arg commands are initially dispatched using the raw draft so command rejection does + /// not consume user input. Once a command is accepted, this helper performs the usual + /// submission preparation (paste expansion, element trimming) and rebases element ranges from + /// full-text offsets to command-arg offsets. + pub(crate) fn prepare_inline_args_submission( + &mut self, + record_history: bool, + ) -> Option<(String, Vec)> { + let (prepared_text, prepared_elements) = self.prepare_submission_text(record_history)?; + let (_, prepared_rest, prepared_rest_offset) = parse_slash_name(&prepared_text)?; + let mut args_elements = Self::slash_command_args_elements( + prepared_rest, + prepared_rest_offset, + &prepared_elements, + ); + let trimmed_rest = prepared_rest.trim(); + args_elements = Self::trim_text_elements(prepared_rest, trimmed_rest, args_elements); + Some((trimmed_rest.to_string(), args_elements)) + } + + fn reject_slash_command_if_unavailable(&self, cmd: SlashCommand) -> bool { + if !self.is_task_running || cmd.available_during_task() { + return false; + } + let message = format!( + "'/{}' is disabled while a task is in progress.", + cmd.command() + ); + self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new( + history_cell::new_error_event(message), + ))); + true + } + + /// Translate full-text element ranges into command-argument ranges. + /// + /// `rest_offset` is the byte offset where `rest` begins in the full text. + fn slash_command_args_elements( + rest: &str, + rest_offset: usize, + text_elements: &[TextElement], + ) -> Vec { + if rest.is_empty() || text_elements.is_empty() { + return Vec::new(); + } + text_elements + .iter() + .filter_map(|elem| { + if elem.byte_range.end <= rest_offset { + return None; + } + let start = elem.byte_range.start.saturating_sub(rest_offset); + let mut end = elem.byte_range.end.saturating_sub(rest_offset); + if start >= rest.len() { + return None; + } + end = end.min(rest.len()); + (start < end).then_some(elem.map_range(|_| ByteRange { start, end })) + }) + .collect() } /// Handle key event when no popup is visible. @@ -2441,6 +2531,7 @@ impl ChatComposer { } fn sync_popups(&mut self) { + self.sync_slash_command_elements(); if !self.popups_enabled() { self.active_popup = ActivePopup::None; return; @@ -2507,6 +2598,88 @@ impl ChatComposer { } } + /// Keep slash command elements aligned with the current first line. + fn sync_slash_command_elements(&mut self) { + if !self.slash_commands_enabled() { + return; + } + let text = self.textarea.text(); + let first_line_end = text.find('\n').unwrap_or(text.len()); + let first_line = &text[..first_line_end]; + let desired_range = self.slash_command_element_range(first_line); + // Slash commands are only valid at byte 0 of the first line. + // Any slash-shaped element not matching the current desired prefix is stale. + let mut has_desired = false; + let mut stale_ranges = Vec::new(); + for elem in self.textarea.text_elements() { + let Some(payload) = elem.placeholder(text) else { + continue; + }; + if payload.strip_prefix('/').is_none() { + continue; + } + let range = elem.byte_range.start..elem.byte_range.end; + if desired_range.as_ref() == Some(&range) { + has_desired = true; + } else { + stale_ranges.push(range); + } + } + + for range in stale_ranges { + self.textarea.remove_element_range(range); + } + + if let Some(range) = desired_range + && !has_desired + { + self.textarea.add_element_range(range); + } + } + + fn slash_command_element_range(&self, first_line: &str) -> Option> { + let (name, _rest, _rest_offset) = parse_slash_name(first_line)?; + if name.contains('/') { + return None; + } + let element_end = 1 + name.len(); + let has_space_after = first_line + .get(element_end..) + .and_then(|tail| tail.chars().next()) + .is_some_and(char::is_whitespace); + if !has_space_after { + return None; + } + if self.is_known_slash_name(name) { + Some(0..element_end) + } else { + None + } + } + + fn is_known_slash_name(&self, name: &str) -> bool { + let is_builtin = slash_commands::find_builtin_command( + name, + self.collaboration_modes_enabled, + self.connectors_enabled, + self.personality_command_enabled, + self.windows_degraded_sandbox_active, + ) + .is_some(); + if is_builtin { + return true; + } + if let Some(rest) = name.strip_prefix(PROMPTS_CMD_PREFIX) + && let Some(prompt_name) = rest.strip_prefix(':') + { + return self + .custom_prompts + .iter() + .any(|prompt| prompt.name == prompt_name); + } + false + } + /// If the cursor is currently within a slash command on the first line, /// extract the command name and the rest of the line after it. /// Returns None if the cursor is outside a slash command. @@ -4582,7 +4755,7 @@ mod tests { InputResult::Command(cmd) => { assert_eq!(cmd.command(), "init"); } - InputResult::CommandWithArgs(_, _) => { + InputResult::CommandWithArgs(_, _, _) => { panic!("expected command dispatch without args for '/init'") } InputResult::Submitted { text, .. } => { @@ -4596,6 +4769,49 @@ mod tests { assert!(composer.textarea.is_empty(), "composer should be cleared"); } + #[test] + fn slash_command_disabled_while_task_running_keeps_text() { + use crossterm::event::KeyCode; + use crossterm::event::KeyEvent; + use crossterm::event::KeyModifiers; + + let (tx, mut rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + composer.set_task_running(true); + composer + .textarea + .set_text_clearing_elements("/review these changes"); + + let (result, _needs_redraw) = + composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + assert_eq!(InputResult::None, result); + assert_eq!("/review these changes", composer.textarea.text()); + + let mut found_error = false; + while let Ok(event) = rx.try_recv() { + if let AppEvent::InsertHistoryCell(cell) = event { + let message = cell + .display_lines(80) + .into_iter() + .map(|line| line.to_string()) + .collect::>() + .join("\n"); + assert!(message.contains("disabled while a task is in progress")); + found_error = true; + break; + } + } + assert!(found_error, "expected error history cell to be sent"); + } + #[test] fn extract_args_supports_quoted_paths_single_arg() { let args = extract_positional_args_for_prompt_line( @@ -4683,7 +4899,7 @@ mod tests { composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); match result { InputResult::Command(cmd) => assert_eq!(cmd.command(), "diff"), - InputResult::CommandWithArgs(_, _) => { + InputResult::CommandWithArgs(_, _, _) => { panic!("expected command dispatch without args for '/diff'") } InputResult::Submitted { text, .. } => { @@ -4697,6 +4913,77 @@ mod tests { assert!(composer.textarea.is_empty()); } + #[test] + fn slash_command_elementizes_on_space() { + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + composer.set_collaboration_modes_enabled(true); + + type_chars_humanlike(&mut composer, &['/', 'p', 'l', 'a', 'n', ' ']); + + let text = composer.textarea.text().to_string(); + let elements = composer.textarea.text_elements(); + assert_eq!(text, "/plan "); + assert_eq!(elements.len(), 1); + assert_eq!(elements[0].placeholder(&text), Some("/plan")); + } + + #[test] + fn slash_command_elementizes_only_known_commands() { + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + composer.set_collaboration_modes_enabled(true); + + type_chars_humanlike(&mut composer, &['/', 'U', 's', 'e', 'r', 's', ' ']); + + let text = composer.textarea.text().to_string(); + let elements = composer.textarea.text_elements(); + assert_eq!(text, "/Users "); + assert!(elements.is_empty()); + } + + #[test] + fn slash_command_element_removed_when_not_at_start() { + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + + type_chars_humanlike(&mut composer, &['/', 'r', 'e', 'v', 'i', 'e', 'w', ' ']); + + let text = composer.textarea.text().to_string(); + let elements = composer.textarea.text_elements(); + assert_eq!(text, "/review "); + assert_eq!(elements.len(), 1); + + composer.textarea.set_cursor(0); + type_chars_humanlike(&mut composer, &['x']); + + let text = composer.textarea.text().to_string(); + let elements = composer.textarea.text_elements(); + assert_eq!(text, "x/review "); + assert!(elements.is_empty()); + } + #[test] fn slash_mention_dispatches_command_and_inserts_at() { use crossterm::event::KeyCode; @@ -4722,7 +5009,7 @@ mod tests { InputResult::Command(cmd) => { assert_eq!(cmd.command(), "mention"); } - InputResult::CommandWithArgs(_, _) => { + InputResult::CommandWithArgs(_, _, _) => { panic!("expected command dispatch without args for '/mention'") } InputResult::Submitted { text, .. } => { @@ -4738,6 +5025,44 @@ mod tests { assert_eq!(composer.textarea.text(), "@"); } + #[test] + fn slash_plan_args_preserve_text_elements() { + use crossterm::event::KeyCode; + use crossterm::event::KeyEvent; + use crossterm::event::KeyModifiers; + + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + composer.set_collaboration_modes_enabled(true); + + type_chars_humanlike(&mut composer, &['/', 'p', 'l', 'a', 'n', ' ']); + let placeholder = local_image_label_text(1); + composer.attach_image(PathBuf::from("/tmp/plan.png")); + + let (result, _needs_redraw) = + composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + match result { + InputResult::CommandWithArgs(cmd, args, text_elements) => { + assert_eq!(cmd.command(), "plan"); + assert_eq!(args, placeholder); + assert_eq!(text_elements.len(), 1); + assert_eq!( + text_elements[0].placeholder(&args), + Some(placeholder.as_str()) + ); + } + _ => panic!("expected CommandWithArgs for /plan with args"), + } + } + /// Behavior: multiple paste operations can coexist; placeholders should be expanded to their /// original content on submission. #[test] diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index a632fd4468..6d3a6755a4 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -218,6 +218,12 @@ impl BottomPane { self.composer.take_mention_paths() } + /// Clear pending attachments and mention paths e.g. when a slash command doesn't submit text. + pub(crate) fn drain_pending_submission_state(&mut self) { + let _ = self.take_recent_submission_images_with_placeholders(); + let _ = self.take_mention_paths(); + } + pub fn set_steer_enabled(&mut self, enabled: bool) { self.composer.set_steer_enabled(enabled); } @@ -404,6 +410,7 @@ impl BottomPane { ) { self.composer .set_text_content(text, text_elements, local_image_paths); + self.composer.move_cursor_to_end(); self.request_redraw(); } @@ -787,6 +794,13 @@ impl BottomPane { .take_recent_submission_images_with_placeholders() } + pub(crate) fn prepare_inline_args_submission( + &mut self, + record_history: bool, + ) -> Option<(String, Vec)> { + self.composer.prepare_inline_args_submission(record_history) + } + fn as_renderable(&'_ self) -> RenderableItem<'_> { if let Some(view) = self.active_view() { RenderableItem::Borrowed(view) diff --git a/codex-rs/tui/src/bottom_pane/textarea.rs b/codex-rs/tui/src/bottom_pane/textarea.rs index 42f7b09bef..a90d5b87c6 100644 --- a/codex-rs/tui/src/bottom_pane/textarea.rs +++ b/codex-rs/tui/src/bottom_pane/textarea.rs @@ -844,6 +844,46 @@ impl TextArea { self.set_cursor(end); } + /// Mark an existing text range as an atomic element without changing the text. + /// + /// This is used to convert already-typed tokens (like `/plan`) into elements + /// so they render and edit atomically. Overlapping or duplicate ranges are ignored. + pub fn add_element_range(&mut self, range: Range) { + let start = self.clamp_pos_to_char_boundary(range.start.min(self.text.len())); + let end = self.clamp_pos_to_char_boundary(range.end.min(self.text.len())); + if start >= end { + return; + } + if self + .elements + .iter() + .any(|e| e.range.start == start && e.range.end == end) + { + return; + } + if self + .elements + .iter() + .any(|e| start < e.range.end && end > e.range.start) + { + return; + } + self.elements.push(TextElement { range: start..end }); + self.elements.sort_by_key(|e| e.range.start); + } + + pub fn remove_element_range(&mut self, range: Range) -> bool { + let start = self.clamp_pos_to_char_boundary(range.start.min(self.text.len())); + let end = self.clamp_pos_to_char_boundary(range.end.min(self.text.len())); + if start >= end { + return false; + } + let len_before = self.elements.len(); + self.elements + .retain(|elem| elem.range.start != start || elem.range.end != end); + len_before != self.elements.len() + } + fn add_element(&mut self, range: Range) { let elem = TextElement { range }; self.elements.push(elem); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 09a7d0fc65..078fbb9785 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -2732,8 +2732,8 @@ impl ChatWidget { InputResult::Command(cmd) => { self.dispatch_command(cmd); } - InputResult::CommandWithArgs(cmd, args) => { - self.dispatch_command_with_args(cmd, args); + InputResult::CommandWithArgs(cmd, args, text_elements) => { + self.dispatch_command_with_args(cmd, args, text_elements); } InputResult::None => {} }, @@ -2783,6 +2783,7 @@ impl ChatWidget { cmd.command() ); self.add_to_history(history_cell::new_error_event(message)); + self.bottom_pane.drain_pending_submission_state(); self.request_redraw(); return; } @@ -3019,7 +3020,16 @@ impl ChatWidget { } } - fn dispatch_command_with_args(&mut self, cmd: SlashCommand, args: String) { + fn dispatch_command_with_args( + &mut self, + cmd: SlashCommand, + args: String, + _text_elements: Vec, + ) { + if !cmd.supports_inline_args() { + self.dispatch_command(cmd); + return; + } if !cmd.available_during_task() && self.bottom_pane.is_task_running() { let message = format!( "'/{}' is disabled while a task is in progress.", @@ -3033,7 +3043,12 @@ impl ChatWidget { let trimmed = args.trim(); match cmd { SlashCommand::Rename if !trimmed.is_empty() => { - let Some(name) = codex_core::util::normalize_thread_name(trimmed) else { + let Some((prepared_args, _prepared_elements)) = + self.bottom_pane.prepare_inline_args_submission(false) + else { + return; + }; + let Some(name) = codex_core::util::normalize_thread_name(&prepared_args) else { self.add_error_message("Thread name cannot be empty.".to_string()); return; }; @@ -3042,20 +3057,50 @@ impl ChatWidget { self.request_redraw(); self.app_event_tx .send(AppEvent::CodexOp(Op::SetThreadName { name })); + self.bottom_pane.drain_pending_submission_state(); } - SlashCommand::Collab | SlashCommand::Plan => { - let _ = trimmed; + SlashCommand::Plan if !trimmed.is_empty() => { self.dispatch_command(cmd); + if self.active_mode_kind() != ModeKind::Plan { + return; + } + let Some((prepared_args, prepared_elements)) = + self.bottom_pane.prepare_inline_args_submission(true) + else { + return; + }; + let user_message = UserMessage { + text: prepared_args, + local_images: self + .bottom_pane + .take_recent_submission_images_with_placeholders(), + text_elements: prepared_elements, + mention_paths: self.bottom_pane.take_mention_paths(), + }; + if self.is_session_configured() { + self.reasoning_buffer.clear(); + self.full_reasoning_buffer.clear(); + self.set_status_header(String::from("Working")); + self.submit_user_message(user_message); + } else { + self.queue_user_message(user_message); + } } SlashCommand::Review if !trimmed.is_empty() => { + let Some((prepared_args, _prepared_elements)) = + self.bottom_pane.prepare_inline_args_submission(false) + else { + return; + }; self.submit_op(Op::Review { review_request: ReviewRequest { target: ReviewTarget::Custom { - instructions: trimmed.to_string(), + instructions: prepared_args, }, user_facing_hint: None, }, }); + self.bottom_pane.drain_pending_submission_state(); } _ => self.dispatch_command(cmd), } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index d24504578c..980d15c339 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -2316,6 +2316,50 @@ async fn plan_slash_command_switches_to_plan_mode() { assert_eq!(chat.current_collaboration_mode(), &initial); } +#[tokio::test] +async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; + chat.set_feature_enabled(Feature::CollaborationModes, true); + + let configured = codex_core::protocol::SessionConfiguredEvent { + session_id: ThreadId::new(), + forked_from_id: None, + thread_name: None, + model: "test-model".to_string(), + model_provider_id: "test-provider".to_string(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::ReadOnly, + cwd: PathBuf::from("/home/user/project"), + reasoning_effort: Some(ReasoningEffortConfig::default()), + history_log_id: 0, + history_entry_count: 0, + initial_messages: None, + rollout_path: None, + }; + chat.handle_codex_event(Event { + id: "configured".into(), + msg: EventMsg::SessionConfigured(configured), + }); + + chat.bottom_pane + .set_composer_text("/plan build the plan".to_string(), Vec::new(), Vec::new()); + chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); + + let items = match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => items, + other => panic!("expected Op::UserTurn, got {other:?}"), + }; + assert_eq!(items.len(), 1); + assert_eq!( + items[0], + UserInput::Text { + text: "build the plan".to_string(), + text_elements: Vec::new(), + } + ); + assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Plan); +} + #[tokio::test] async fn collaboration_modes_defaults_to_code_on_startup() { let codex_home = tempdir().expect("tempdir"); diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index 4b68645aef..a71b1ddbe5 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -87,6 +87,14 @@ impl SlashCommand { self.into() } + /// Whether this command supports inline args (for example `/review ...`). + pub fn supports_inline_args(self) -> bool { + matches!( + self, + SlashCommand::Review | SlashCommand::Rename | SlashCommand::Plan + ) + } + /// Whether this command can be run while a task is in progress. pub fn available_during_task(self) -> bool { match self { @@ -103,6 +111,7 @@ impl SlashCommand { | SlashCommand::ElevateSandbox | SlashCommand::Experimental | SlashCommand::Review + | SlashCommand::Plan | SlashCommand::Logout => false, SlashCommand::Diff | SlashCommand::Rename @@ -117,7 +126,6 @@ impl SlashCommand { | SlashCommand::Exit => true, SlashCommand::Rollout => true, SlashCommand::TestApproval => true, - SlashCommand::Plan => true, SlashCommand::Collab => true, SlashCommand::Agent => true, } diff --git a/docs/tui-chat-composer.md b/docs/tui-chat-composer.md index 01b5334d94..6211cbcfce 100644 --- a/docs/tui-chat-composer.md +++ b/docs/tui-chat-composer.md @@ -48,6 +48,8 @@ The solution is to detect paste-like _bursts_ and buffer them into a single expl history navigation, etc). - After handling the key, `sync_popups()` runs so popup visibility/filters stay consistent with the latest text + cursor. +- When a slash command name is completed and the user types a space, the `/command` token is + promoted into a text element so it renders distinctly and edits atomically. ### History navigation (↑/↓) @@ -105,6 +107,9 @@ There are multiple submission paths, but they share the same core rules: 5. Clears pending pastes on success and suppresses submission if the final text is empty and there are no attachments. +The same preparation path is reused for slash commands with arguments (for example `/plan` and +`/review`) so pasted content and text elements are preserved when extracting args. + ### Numeric auto-submit path When the slash popup is open and the first line matches a numeric-only custom prompt with From 9d976962ec6baa3fc3df7370927053b86591b1f7 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 2 Feb 2026 10:06:43 -0800 Subject: [PATCH 071/139] Add credits tooltip (#10274) --- codex-rs/tui/src/app.rs | 1 + codex-rs/tui/src/chatwidget.rs | 3 +++ codex-rs/tui/src/history_cell.rs | 4 +++- codex-rs/tui/src/tooltips.rs | 32 ++++++++++++++++++++++++++++++-- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 0e155fdc00..8dc2276b75 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -2939,6 +2939,7 @@ mod tests { app.chat_widget.current_model(), event, is_first, + None, )) as Arc }; diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 078fbb9785..eb2d1ad965 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -815,6 +815,9 @@ impl ChatWidget { &model_for_header, event, self.show_welcome_banner, + self.auth_manager + .auth_cached() + .and_then(|auth| auth.account_plan_type()), ); self.apply_session_info_cell(session_info_cell); diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 934c9506f5..eb1de452a9 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -46,6 +46,7 @@ use codex_core::protocol::McpInvocation; use codex_core::protocol::SessionConfiguredEvent; use codex_core::web_search::web_search_detail; use codex_otel::RuntimeMetricsSummary; +use codex_protocol::account::PlanType; use codex_protocol::models::WebSearchAction; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::PlanItemArg; @@ -943,6 +944,7 @@ pub(crate) fn new_session_info( requested_model: &str, event: SessionConfiguredEvent, is_first_event: bool, + auth_plan: Option, ) -> SessionInfoCell { let SessionConfiguredEvent { model, @@ -995,7 +997,7 @@ pub(crate) fn new_session_info( parts.push(Box::new(PlainHistoryCell { lines: help_lines })); } else { if config.show_tooltips - && let Some(tooltips) = tooltips::random_tooltip().map(TooltipHistoryCell::new) + && let Some(tooltips) = tooltips::get_tooltip(auth_plan).map(TooltipHistoryCell::new) { parts.push(Box::new(tooltips)); } diff --git a/codex-rs/tui/src/tooltips.rs b/codex-rs/tui/src/tooltips.rs index 1b59eb4f80..b59a478a4d 100644 --- a/codex-rs/tui/src/tooltips.rs +++ b/codex-rs/tui/src/tooltips.rs @@ -1,9 +1,18 @@ use codex_core::features::FEATURES; +use codex_protocol::account::PlanType; use lazy_static::lazy_static; use rand::Rng; const ANNOUNCEMENT_TIP_URL: &str = "https://raw.githubusercontent.com/openai/codex/main/announcement_tip.toml"; + +const PAID_TOOLTIP: &str = + "*New* Try the **Codex App** with 2x rate limits until *April 2nd*. https://chatgpt.com/codex"; +const OTHER_TOOLTIP: &str = + "*New* Build faster with the **Codex App**. Try it now. https://chatgpt.com/codex"; +const FREE_GO_TOOLTIP: &str = + "*New* Codex is included in your plan for free through *March 2nd* – let’s build together."; + const RAW_TOOLTIPS: &str = include_str!("../tooltips.txt"); lazy_static! { @@ -28,11 +37,30 @@ fn experimental_tooltips() -> Vec<&'static str> { } /// Pick a random tooltip to show to the user when starting Codex. -pub(crate) fn random_tooltip() -> Option { +pub(crate) fn get_tooltip(plan: Option) -> Option { + let mut rng = rand::rng(); + + // Leave small chance for a random tooltip to be shown. + if rng.random_ratio(8, 10) { + match plan { + Some(PlanType::Plus) + | Some(PlanType::Business) + | Some(PlanType::Team) + | Some(PlanType::Enterprise) + | Some(PlanType::Pro) => { + return Some(PAID_TOOLTIP.to_string()); + } + Some(PlanType::Go) | Some(PlanType::Free) => { + return Some(FREE_GO_TOOLTIP.to_string()); + } + _ => return Some(OTHER_TOOLTIP.to_string()), + } + } + if let Some(announcement) = announcement::fetch_announcement_tip() { return Some(announcement); } - let mut rng = rand::rng(); + pick_tooltip(&mut rng).map(str::to_string) } From 0b460eda32c031430cc882894acb22ca9cb5480a Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 2 Feb 2026 19:13:48 +0100 Subject: [PATCH 072/139] chore: ignore synthetic messages (#10394) This will be fixed once this is settled: https://www.notion.so/openai/Artificial-context-management-2fb8e50b62b080db8b8ed93b3b19d1a2#2fb8e50b62b080d2bffce2dd1e60972b --- codex-rs/core/src/rollout/list.rs | 7 ++++++- codex-rs/core/src/session_prefix.rs | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index f42b4795cd..16bd5b16ed 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -15,7 +15,9 @@ use uuid::Uuid; use super::ARCHIVED_SESSIONS_SUBDIR; use super::SESSIONS_SUBDIR; +use crate::instructions::UserInstructions; use crate::protocol::EventMsg; +use crate::session_prefix::is_session_prefix_content; use crate::state_db; use codex_file_search as file_search; use codex_protocol::ThreadId; @@ -982,9 +984,12 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result bool { let lowered = trimmed.to_ascii_lowercase(); lowered.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG) || lowered.starts_with(TURN_ABORTED_OPEN_TAG) } + +/// Returns true if `text` starts with a session prefix marker (case-insensitive). +pub(crate) fn is_session_prefix_content(content: &[ContentItem]) -> bool { + if let [ContentItem::InputText { text }] = content { + is_session_prefix(text) + } else { + false + } +} From 34c0534f6eb85eb1976862d666cef3af165848d6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 2 Feb 2026 19:26:58 +0000 Subject: [PATCH 073/139] feat: drop sqlx logging (#10398) --- codex-rs/Cargo.lock | 1 + codex-rs/state/Cargo.toml | 1 + codex-rs/state/src/runtime.rs | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 4614e5f24f..57186f82f1 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1901,6 +1901,7 @@ dependencies = [ "codex-otel", "codex-protocol", "dirs", + "log", "owo-colors", "pretty_assertions", "serde", diff --git a/codex-rs/state/Cargo.toml b/codex-rs/state/Cargo.toml index 6019b5888b..837d451387 100644 --- a/codex-rs/state/Cargo.toml +++ b/codex-rs/state/Cargo.toml @@ -11,6 +11,7 @@ clap = { workspace = true, features = ["derive", "env"] } codex-otel = { workspace = true } codex-protocol = { workspace = true } dirs = { workspace = true } +log = { workspace = true } owo-colors = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 39c79c32b9..6f2e128967 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -17,6 +17,8 @@ use chrono::Utc; use codex_otel::OtelManager; use codex_protocol::ThreadId; use codex_protocol::protocol::RolloutItem; +use log::LevelFilter; +use sqlx::ConnectOptions; use sqlx::QueryBuilder; use sqlx::Row; use sqlx::Sqlite; @@ -511,7 +513,8 @@ async fn open_sqlite(path: &Path) -> anyhow::Result { .create_if_missing(true) .journal_mode(SqliteJournalMode::Wal) .synchronous(SqliteSynchronous::Normal) - .busy_timeout(Duration::from_secs(5)); + .busy_timeout(Duration::from_secs(5)) + .log_statements(LevelFilter::Off); let pool = SqlitePoolOptions::new() .max_connections(5) .connect_with(options) From 74327fa59ce3b4ac2432e822d43ff43e5332097c Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 2 Feb 2026 11:35:11 -0800 Subject: [PATCH 074/139] Select experimental features with space (#10281) --- .../tui/src/bottom_pane/experimental_features_view.rs | 11 ++++++++--- ...hatwidget__tests__experimental_features_popup.snap | 2 +- codex-rs/tui/src/chatwidget/tests.rs | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/experimental_features_view.rs b/codex-rs/tui/src/bottom_pane/experimental_features_view.rs index 07cfce163f..1fde95b08f 100644 --- a/codex-rs/tui/src/bottom_pane/experimental_features_view.rs +++ b/codex-rs/tui/src/bottom_pane/experimental_features_view.rs @@ -175,11 +175,16 @@ impl BottomPaneView for ExperimentalFeaturesView { .. } => self.move_down(), KeyEvent { - code: KeyCode::Enter, + code: KeyCode::Char(' '), modifiers: KeyModifiers::NONE, .. } => self.toggle_selected(), KeyEvent { + code: KeyCode::Enter, + modifiers: KeyModifiers::NONE, + .. + } + | KeyEvent { code: KeyCode::Esc, .. } => { self.on_ctrl_c(); @@ -287,9 +292,9 @@ impl Renderable for ExperimentalFeaturesView { fn experimental_popup_hint_line() -> Line<'static> { Line::from(vec![ "Press ".into(), + key_hint::plain(KeyCode::Char(' ')).into(), + " to select or ".into(), key_hint::plain(KeyCode::Enter).into(), - " to toggle or ".into(), - key_hint::plain(KeyCode::Esc).into(), " to save for next conversation".into(), ]) } diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__experimental_features_popup.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__experimental_features_popup.snap index 4101717cf9..9cb2d78522 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__experimental_features_popup.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__experimental_features_popup.snap @@ -8,4 +8,4 @@ expression: popup › [ ] Ghost snapshots Capture undo snapshots each turn. [x] Shell tool Allow the model to run shell commands. - Press enter to toggle or esc to save for next conversation + Press space to select or enter to save for next conversation diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 980d15c339..a70d93a2c4 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -3036,14 +3036,14 @@ async fn experimental_features_toggle_saves_on_exit() { ); chat.bottom_pane.show_view(Box::new(view)); - chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + chat.handle_key_event(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE)); assert!( rx.try_recv().is_err(), - "expected no updates until exiting the popup" + "expected no updates until saving the popup" ); - chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)); + chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); let mut updates = None; while let Ok(event) = rx.try_recv() { From 059d386f0316fe8f4b73959a8b1c756d17e48bfd Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 2 Feb 2026 20:30:01 +0000 Subject: [PATCH 075/139] feat: add `--experimental` to `generate-ts` (#10402) Adding a `--experimental` flag to the `generate-ts` fct in the app-sever. It can be called through one of those 2 command ``` just write-app-server-schema --experimental codex app-server generate-ts --experimental ``` --- .../src/bin/write_schema_fixtures.rs | 24 ++++++++++++------ codex-rs/app-server-protocol/src/lib.rs | 2 ++ .../src/schema_fixtures.rs | 25 +++++++++++++++++-- codex-rs/app-server/README.md | 2 ++ justfile | 4 +-- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs b/codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs index a8e36d1ee8..789d30cea2 100644 --- a/codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs +++ b/codex-rs/app-server-protocol/src/bin/write_schema_fixtures.rs @@ -13,6 +13,10 @@ struct Args { /// Optional path to the Prettier executable to format generated TypeScript files. #[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")] prettier: Option, + + /// Include experimental API methods and fields in generated fixtures. + #[arg(long = "experimental")] + experimental: bool, } fn main() -> Result<()> { @@ -22,11 +26,17 @@ fn main() -> Result<()> { .schema_root .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("schema")); - codex_app_server_protocol::write_schema_fixtures(&schema_root, args.prettier.as_deref()) - .with_context(|| { - format!( - "failed to regenerate schema fixtures under {}", - schema_root.display() - ) - }) + codex_app_server_protocol::write_schema_fixtures_with_options( + &schema_root, + args.prettier.as_deref(), + codex_app_server_protocol::SchemaFixtureOptions { + experimental_api: args.experimental, + }, + ) + .with_context(|| { + format!( + "failed to regenerate schema fixtures under {}", + schema_root.display() + ) + }) } diff --git a/codex-rs/app-server-protocol/src/lib.rs b/codex-rs/app-server-protocol/src/lib.rs index 7b3f17045b..54a933bb74 100644 --- a/codex-rs/app-server-protocol/src/lib.rs +++ b/codex-rs/app-server-protocol/src/lib.rs @@ -16,5 +16,7 @@ pub use protocol::common::*; pub use protocol::thread_history::*; pub use protocol::v1::*; pub use protocol::v2::*; +pub use schema_fixtures::SchemaFixtureOptions; pub use schema_fixtures::read_schema_fixture_tree; pub use schema_fixtures::write_schema_fixtures; +pub use schema_fixtures::write_schema_fixtures_with_options; diff --git a/codex-rs/app-server-protocol/src/schema_fixtures.rs b/codex-rs/app-server-protocol/src/schema_fixtures.rs index efd8bb2cb0..918011e95c 100644 --- a/codex-rs/app-server-protocol/src/schema_fixtures.rs +++ b/codex-rs/app-server-protocol/src/schema_fixtures.rs @@ -6,6 +6,11 @@ use std::collections::BTreeMap; use std::path::Path; use std::path::PathBuf; +#[derive(Clone, Copy, Debug, Default)] +pub struct SchemaFixtureOptions { + pub experimental_api: bool, +} + pub fn read_schema_fixture_tree(schema_root: &Path) -> Result>> { let typescript_root = schema_root.join("typescript"); let json_root = schema_root.join("json"); @@ -26,14 +31,30 @@ pub fn read_schema_fixture_tree(schema_root: &Path) -> Result) -> Result<()> { + write_schema_fixtures_with_options(schema_root, prettier, SchemaFixtureOptions::default()) +} + +/// Regenerates schema fixtures with configurable options. +pub fn write_schema_fixtures_with_options( + schema_root: &Path, + prettier: Option<&Path>, + options: SchemaFixtureOptions, +) -> Result<()> { let typescript_out_dir = schema_root.join("typescript"); let json_out_dir = schema_root.join("json"); ensure_empty_dir(&typescript_out_dir)?; ensure_empty_dir(&json_out_dir)?; - crate::generate_ts(&typescript_out_dir, prettier)?; - crate::generate_json(&json_out_dir)?; + crate::generate_ts_with_options( + &typescript_out_dir, + prettier, + crate::GenerateTsOptions { + experimental_api: options.experimental_api, + ..crate::GenerateTsOptions::default() + }, + )?; + crate::generate_json_with_experimental(&json_out_dir, options.experimental_api)?; Ok(()) } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 67bb36d149..1d5b6d15fd 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -788,6 +788,8 @@ At runtime, clients must send `initialize` with `capabilities.experimentalApi = ```bash just write-app-server-schema + # Include experimental API fields/methods in fixtures. + just write-app-server-schema --experimental ``` 5. Verify the protocol crate: diff --git a/justfile b/justfile index 67577c7c7f..024a31ba2a 100644 --- a/justfile +++ b/justfile @@ -69,8 +69,8 @@ write-config-schema: cargo run -p codex-core --bin codex-write-config-schema # Regenerate vendored app-server protocol schema artifacts. -write-app-server-schema: - cargo run -p codex-app-server-protocol --bin write_schema_fixtures +write-app-server-schema *args: + cargo run -p codex-app-server-protocol --bin write_schema_fixtures -- "$@" # Tail logs from the state SQLite database log *args: From f50c8b2f81d69e49d1f6eafaeebf0f6ba2bbba52 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 2 Feb 2026 12:30:17 -0800 Subject: [PATCH 076/139] fix: unsafe auto-approval of git commands (#10258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes https://github.com/openai/codex/issues/10160 and some more. ## Description Hardens Git command safety to prevent approval bypasses for destructive or write-capable invocations (branch delete, risky push forms, output/config-override flags), so these commands no longer auto-run as “safe.” - `git branch -d` variants (especially in worktrees / with global options like -C / -c) - `git show|diff|log --output` ... style file-write flags - risky Git config override flags (-c, --config-env) that can trigger external execution - dangerous push forms that weren’t fully caught (`--force*`, `--delete`, `+refspec`, `:refspec`) - grouped short-flag delete forms (e.g. stacked branch flags containing `d/D`) will fast follow with a common git policy to bring windows to parity. --------- Co-authored-by: Eric Traut --- .../command_safety/is_dangerous_command.rs | 278 +++++++++++++++++- .../src/command_safety/is_safe_command.rs | 179 ++++++++++- codex-rs/core/src/exec_policy.rs | 24 ++ 3 files changed, 468 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/src/command_safety/is_dangerous_command.rs b/codex-rs/core/src/command_safety/is_dangerous_command.rs index 256f36c600..3e2c669c44 100644 --- a/codex-rs/core/src/command_safety/is_dangerous_command.rs +++ b/codex-rs/core/src/command_safety/is_dangerous_command.rs @@ -27,12 +27,100 @@ pub fn command_might_be_dangerous(command: &[String]) -> bool { false } +fn is_git_global_option_with_value(arg: &str) -> bool { + matches!( + arg, + "-C" | "-c" + | "--config-env" + | "--exec-path" + | "--git-dir" + | "--namespace" + | "--super-prefix" + | "--work-tree" + ) +} + +fn is_git_global_option_with_inline_value(arg: &str) -> bool { + matches!( + arg, + s if s.starts_with("--config-env=") + || s.starts_with("--exec-path=") + || s.starts_with("--git-dir=") + || s.starts_with("--namespace=") + || s.starts_with("--super-prefix=") + || s.starts_with("--work-tree=") + ) || ((arg.starts_with("-C") || arg.starts_with("-c")) && arg.len() > 2) +} + +/// Find the first matching git subcommand, skipping known global options that +/// may appear before it (e.g., `-C`, `-c`, `--git-dir`). +/// +/// Shared with `is_safe_command` to avoid git-global-option bypasses. +pub(crate) fn find_git_subcommand<'a>( + command: &'a [String], + subcommands: &[&str], +) -> Option<(usize, &'a str)> { + let cmd0 = command.first().map(String::as_str)?; + if !cmd0.ends_with("git") { + return None; + } + + let mut skip_next = false; + for (idx, arg) in command.iter().enumerate().skip(1) { + if skip_next { + skip_next = false; + continue; + } + + let arg = arg.as_str(); + + if is_git_global_option_with_inline_value(arg) { + continue; + } + + if is_git_global_option_with_value(arg) { + skip_next = true; + continue; + } + + if arg == "--" || arg.starts_with('-') { + continue; + } + + if subcommands.contains(&arg) { + return Some((idx, arg)); + } + + // In git, the first non-option token is the subcommand. If it isn't + // one of the subcommands we're looking for, we must stop scanning to + // avoid misclassifying later positional args (e.g., branch names). + return None; + } + + None +} + fn is_dangerous_to_call_with_exec(command: &[String]) -> bool { let cmd0 = command.first().map(String::as_str); match cmd0 { - Some(cmd) if cmd.ends_with("git") || cmd.ends_with("/git") => { - matches!(command.get(1).map(String::as_str), Some("reset" | "rm")) + Some(cmd) if cmd.ends_with("git") => { + let Some((subcommand_idx, subcommand)) = + find_git_subcommand(command, &["reset", "rm", "branch", "push", "clean"]) + else { + return false; + }; + + match subcommand { + "reset" | "rm" => true, + "branch" => git_branch_is_delete(&command[subcommand_idx + 1..]), + "push" => git_push_is_dangerous(&command[subcommand_idx + 1..]), + "clean" => git_clean_is_force(&command[subcommand_idx + 1..]), + other => { + debug_assert!(false, "unexpected git subcommand from matcher: {other}"); + false + } + } } Some("rm") => matches!(command.get(1).map(String::as_str), Some("-f" | "-rf")), @@ -45,6 +133,48 @@ fn is_dangerous_to_call_with_exec(command: &[String]) -> bool { } } +fn git_branch_is_delete(branch_args: &[String]) -> bool { + // Git allows stacking short flags (for example, `-dv` or `-vd`). Treat any + // short-flag group containing `d`/`D` as a delete flag. + branch_args.iter().map(String::as_str).any(|arg| { + matches!(arg, "-d" | "-D" | "--delete") + || arg.starts_with("--delete=") + || short_flag_group_contains(arg, 'd') + || short_flag_group_contains(arg, 'D') + }) +} + +fn short_flag_group_contains(arg: &str, target: char) -> bool { + arg.starts_with('-') && !arg.starts_with("--") && arg.chars().skip(1).any(|c| c == target) +} + +fn git_push_is_dangerous(push_args: &[String]) -> bool { + push_args.iter().map(String::as_str).any(|arg| { + matches!( + arg, + "--force" | "--force-with-lease" | "--force-if-includes" | "--delete" | "-f" | "-d" + ) || arg.starts_with("--force-with-lease=") + || arg.starts_with("--force-if-includes=") + || arg.starts_with("--delete=") + || short_flag_group_contains(arg, 'f') + || short_flag_group_contains(arg, 'd') + || git_push_refspec_is_dangerous(arg) + }) +} + +fn git_push_refspec_is_dangerous(arg: &str) -> bool { + // `+` forces updates and `:` deletes remote refs. + (arg.starts_with('+') || arg.starts_with(':')) && arg.len() > 1 +} + +fn git_clean_is_force(clean_args: &[String]) -> bool { + clean_args.iter().map(String::as_str).any(|arg| { + matches!(arg, "--force" | "-f") + || arg.starts_with("--force=") + || short_flag_group_contains(arg, 'f') + }) +} + #[cfg(test)] mod tests { use super::*; @@ -63,7 +193,7 @@ mod tests { assert!(command_might_be_dangerous(&vec_str(&[ "bash", "-lc", - "git reset --hard" + "git reset --hard", ]))); } @@ -72,7 +202,7 @@ mod tests { assert!(command_might_be_dangerous(&vec_str(&[ "zsh", "-lc", - "git reset --hard" + "git reset --hard", ]))); } @@ -86,14 +216,14 @@ mod tests { assert!(!command_might_be_dangerous(&vec_str(&[ "bash", "-lc", - "git status" + "git status", ]))); } #[test] fn sudo_git_reset_is_dangerous() { assert!(command_might_be_dangerous(&vec_str(&[ - "sudo", "git", "reset", "--hard" + "sudo", "git", "reset", "--hard", ]))); } @@ -102,7 +232,141 @@ mod tests { assert!(command_might_be_dangerous(&vec_str(&[ "/usr/bin/git", "reset", - "--hard" + "--hard", + ]))); + } + + #[test] + fn git_branch_delete_is_dangerous() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "branch", "-d", "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "branch", "-D", "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "bash", + "-lc", + "git branch --delete feature", + ]))); + } + + #[test] + fn git_branch_delete_with_stacked_short_flags_is_dangerous() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "branch", "-dv", "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "branch", "-vd", "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "branch", "-vD", "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "branch", "-Dvv", "feature", + ]))); + } + + #[test] + fn git_branch_delete_with_global_options_is_dangerous() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "-C", ".", "branch", "-d", "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", + "-c", + "color.ui=false", + "branch", + "-D", + "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "bash", + "-lc", + "git -C . branch -d feature", + ]))); + } + + #[test] + fn git_checkout_reset_is_not_dangerous() { + // The first non-option token is "checkout", so later positional args + // like branch names must not be treated as subcommands. + assert!(!command_might_be_dangerous(&vec_str(&[ + "git", "checkout", "reset", + ]))); + } + + #[test] + fn git_push_force_is_dangerous() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "push", "--force", "origin", "main", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "push", "-f", "origin", "main", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", + "-C", + ".", + "push", + "--force-with-lease", + "origin", + "main", + ]))); + } + + #[test] + fn git_push_plus_refspec_is_dangerous() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "push", "origin", "+main", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", + "push", + "origin", + "+refs/heads/main:refs/heads/main", + ]))); + } + + #[test] + fn git_push_delete_flag_is_dangerous() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "push", "--delete", "origin", "feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "push", "-d", "origin", "feature", + ]))); + } + + #[test] + fn git_push_delete_refspec_is_dangerous() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "push", "origin", ":feature", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "bash", + "-lc", + "git push origin :feature", + ]))); + } + + #[test] + fn git_push_without_force_is_not_dangerous() { + assert!(!command_might_be_dangerous(&vec_str(&[ + "git", "push", "origin", "main", + ]))); + } + + #[test] + fn git_clean_force_is_dangerous_even_when_f_is_not_first_flag() { + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "clean", "-fdx", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "clean", "-xdf", + ]))); + assert!(command_might_be_dangerous(&vec_str(&[ + "git", "clean", "--force", ]))); } diff --git a/codex-rs/core/src/command_safety/is_safe_command.rs b/codex-rs/core/src/command_safety/is_safe_command.rs index 01a52026e2..e52079c74b 100644 --- a/codex-rs/core/src/command_safety/is_safe_command.rs +++ b/codex-rs/core/src/command_safety/is_safe_command.rs @@ -1,4 +1,8 @@ use crate::bash::parse_shell_lc_plain_commands; +// Find the first matching git subcommand, skipping known global options that +// may appear before it (e.g., `-C`, `-c`, `--git-dir`). +// Implemented in `is_dangerous_command` and shared here. +use crate::command_safety::is_dangerous_command::find_git_subcommand; use crate::command_safety::windows_safe_commands::is_safe_command_windows; pub fn is_known_safe_command(command: &[String]) -> bool { @@ -131,13 +135,36 @@ fn is_safe_to_call_with_exec(command: &[String]) -> bool { } // Git - Some("git") => matches!( - command.get(1).map(String::as_str), - Some("branch" | "status" | "log" | "diff" | "show") - ), + Some("git") => { + // Global config overrides like `-c core.pager=...` can force git + // to execute arbitrary external commands. With no sandboxing, we + // should always prompt in those cases. + if git_has_config_override_global_option(command) { + return false; + } - // Rust - Some("cargo") if command.get(1).map(String::as_str) == Some("check") => true, + let Some((subcommand_idx, subcommand)) = + find_git_subcommand(command, &["status", "log", "diff", "show", "branch"]) + else { + return false; + }; + + let subcommand_args = &command[subcommand_idx + 1..]; + + match subcommand { + "status" | "log" | "diff" | "show" => { + git_subcommand_args_are_read_only(subcommand_args) + } + "branch" => { + git_subcommand_args_are_read_only(subcommand_args) + && git_branch_is_read_only(subcommand_args) + } + other => { + debug_assert!(false, "unexpected git subcommand from matcher: {other}"); + false + } + } + } // Special-case `sed -n {N|M,N}p` Some("sed") @@ -155,6 +182,60 @@ fn is_safe_to_call_with_exec(command: &[String]) -> bool { } } +// Treat `git branch` as safe only when the arguments clearly indicate +// a read-only query, not a branch mutation (create/rename/delete). +fn git_branch_is_read_only(branch_args: &[String]) -> bool { + if branch_args.is_empty() { + // `git branch` with no additional args lists branches. + return true; + } + + let mut saw_read_only_flag = false; + for arg in branch_args.iter().map(String::as_str) { + match arg { + "--list" | "-l" | "--show-current" | "-a" | "--all" | "-r" | "--remotes" | "-v" + | "-vv" | "--verbose" => { + saw_read_only_flag = true; + } + _ if arg.starts_with("--format=") => { + saw_read_only_flag = true; + } + _ => { + // Any other flag or positional argument may create, rename, or delete branches. + return false; + } + } + } + + saw_read_only_flag +} + +fn git_has_config_override_global_option(command: &[String]) -> bool { + command.iter().map(String::as_str).any(|arg| { + matches!(arg, "-c" | "--config-env") + || (arg.starts_with("-c") && arg.len() > 2) + || arg.starts_with("--config-env=") + }) +} + +fn git_subcommand_args_are_read_only(args: &[String]) -> bool { + // Flags that can write to disk or execute external tools should never be + // auto-approved on an unsandboxed machine. + const UNSAFE_GIT_FLAGS: &[&str] = &[ + "--output", + "--ext-diff", + "--textconv", + "--exec", + "--paginate", + ]; + + !args.iter().map(String::as_str).any(|arg| { + UNSAFE_GIT_FLAGS.contains(&arg) + || arg.starts_with("--output=") + || arg.starts_with("--exec=") + }) +} + // (bash parsing helpers implemented in crate::bash) /* ---------------------------------------------------------- @@ -207,6 +288,12 @@ mod tests { fn known_safe_examples() { assert!(is_safe_to_call_with_exec(&vec_str(&["ls"]))); assert!(is_safe_to_call_with_exec(&vec_str(&["git", "status"]))); + assert!(is_safe_to_call_with_exec(&vec_str(&["git", "branch"]))); + assert!(is_safe_to_call_with_exec(&vec_str(&[ + "git", + "branch", + "--show-current" + ]))); assert!(is_safe_to_call_with_exec(&vec_str(&["base64"]))); assert!(is_safe_to_call_with_exec(&vec_str(&[ "sed", "-n", "1,5p", "file.txt" @@ -231,6 +318,86 @@ mod tests { } } + #[test] + fn git_branch_mutating_flags_are_not_safe() { + assert!(!is_known_safe_command(&vec_str(&[ + "git", "branch", "-d", "feature" + ]))); + assert!(!is_known_safe_command(&vec_str(&[ + "git", + "branch", + "new-branch" + ]))); + } + + #[test] + fn git_branch_global_options_respect_safety_rules() { + use pretty_assertions::assert_eq; + + assert_eq!( + is_known_safe_command(&vec_str(&["git", "-C", ".", "branch", "--show-current"])), + true + ); + assert_eq!( + is_known_safe_command(&vec_str(&["git", "-C", ".", "branch", "-d", "feature"])), + false + ); + assert_eq!( + is_known_safe_command(&vec_str(&["bash", "-lc", "git -C . branch -d feature",])), + false + ); + } + + #[test] + fn git_first_positional_is_the_subcommand() { + // In git, the first non-option token is the subcommand. Later positional + // args (like branch names) must not be treated as subcommands. + assert!(!is_known_safe_command(&vec_str(&[ + "git", "checkout", "status", + ]))); + } + + #[test] + fn git_output_and_config_override_flags_are_not_safe() { + assert!(!is_known_safe_command(&vec_str(&[ + "git", + "log", + "--output=/tmp/git-log-out-test", + "-n", + "1", + ]))); + assert!(!is_known_safe_command(&vec_str(&[ + "git", + "diff", + "--output", + "/tmp/git-diff-out-test", + ]))); + assert!(!is_known_safe_command(&vec_str(&[ + "git", + "show", + "--output=/tmp/git-show-out-test", + "HEAD", + ]))); + assert!(!is_known_safe_command(&vec_str(&[ + "git", + "-c", + "core.pager=cat", + "log", + "-n", + "1", + ]))); + assert!(!is_known_safe_command(&vec_str(&[ + "git", + "-ccore.pager=cat", + "status", + ]))); + } + + #[test] + fn cargo_check_is_not_safe() { + assert!(!is_known_safe_command(&vec_str(&["cargo", "check"]))); + } + #[test] fn zsh_lc_safe_command_sequence() { assert!(is_known_safe_command(&vec_str(&["zsh", "-lc", "ls"]))); diff --git a/codex-rs/core/src/exec_policy.rs b/codex-rs/core/src/exec_policy.rs index c0cb7f8fcf..a2e1e8d40d 100644 --- a/codex-rs/core/src/exec_policy.rs +++ b/codex-rs/core/src/exec_policy.rs @@ -1280,6 +1280,30 @@ prefix_rule( ); } + #[tokio::test] + async fn dangerous_git_push_requires_approval_in_danger_full_access() { + let command = vec_str(&["git", "push", "origin", "+main"]); + let manager = ExecPolicyManager::default(); + let requirement = manager + .create_exec_approval_requirement_for_command(ExecApprovalRequest { + features: &Features::with_defaults(), + command: &command, + approval_policy: AskForApproval::OnRequest, + sandbox_policy: &SandboxPolicy::DangerFullAccess, + sandbox_permissions: SandboxPermissions::UseDefault, + prefix_rule: None, + }) + .await; + + assert_eq!( + requirement, + ExecApprovalRequirement::NeedsApproval { + reason: None, + proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(command)), + } + ); + } + fn vec_str(items: &[&str]) -> Vec { items.iter().map(std::string::ToString::to_string).collect() } From 0f15ed43256e229d40bac944cbdcf0424fa75ae0 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 2 Feb 2026 13:13:14 -0800 Subject: [PATCH 077/139] Updated labeler workflow prompt to include "app" label (#10411) Support for desktop app issues --- .github/workflows/issue-labeler.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml index da77812fec..8146b8c5bd 100644 --- a/.github/workflows/issue-labeler.yml +++ b/.github/workflows/issue-labeler.yml @@ -38,9 +38,10 @@ jobs: - If applicable, add one of the following labels to specify which sub-product or product surface the issue relates to. 1. CLI — the Codex command line interface. 2. extension — VS Code (or other IDE) extension-specific issues. - 3. codex-web — Issues targeting the Codex web UI/Cloud experience. - 4. github-action — Issues with the Codex GitHub action. - 5. iOS — Issues with the Codex iOS app. + 3. app - Issues related to the Codex desktop application. + 4. codex-web — Issues targeting the Codex web UI/Cloud experience. + 5. github-action — Issues with the Codex GitHub action. + 6. iOS — Issues with the Codex iOS app. - Additionally add zero or more of the following labels that are relevant to the issue content. Prefer a small set of precise labels over many broad ones. 1. windows-os — Bugs or friction specific to Windows environments (always when PowerShell is mentioned, path handling, copy/paste, OS-specific auth or tooling failures). From a5066bef7899a99279ed9172a8d31692d4146c0c Mon Sep 17 00:00:00 2001 From: iceweasel-oai Date: Mon, 2 Feb 2026 15:31:08 -0800 Subject: [PATCH 078/139] emit a separate metric when the user cancels UAT during elevated setup (#10399) Currently this shows up as elevated setup failure, which isn't quite accurate. --- codex-rs/core/src/windows_sandbox.rs | 19 +++++++++++++++++++ codex-rs/tui/src/app.rs | 4 +++- .../windows-sandbox-rs/src/setup_error.rs | 3 +++ .../src/setup_orchestrator.rs | 8 +++++++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/windows_sandbox.rs b/codex-rs/core/src/windows_sandbox.rs index 7b9451c246..9a34a9f90f 100644 --- a/codex-rs/core/src/windows_sandbox.rs +++ b/codex-rs/core/src/windows_sandbox.rs @@ -65,6 +65,25 @@ pub fn elevated_setup_failure_details(_err: &anyhow::Error) -> Option<(String, S None } +#[cfg(target_os = "windows")] +pub fn elevated_setup_failure_metric_name(err: &anyhow::Error) -> &'static str { + if codex_windows_sandbox::extract_setup_failure(err).is_some_and(|failure| { + matches!( + failure.code, + codex_windows_sandbox::SetupErrorCode::OrchestratorHelperLaunchCanceled + ) + }) { + "codex.windows_sandbox.elevated_setup_canceled" + } else { + "codex.windows_sandbox.elevated_setup_failure" + } +} + +#[cfg(not(target_os = "windows"))] +pub fn elevated_setup_failure_metric_name(_err: &anyhow::Error) -> &'static str { + panic!("elevated_setup_failure_metric_name is only supported on Windows") +} + #[cfg(target_os = "windows")] pub fn run_elevated_setup( policy: &SandboxPolicy, diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 8dc2276b75..48ef0a772d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1700,7 +1700,9 @@ impl App { tags.push(("message", message)); } otel_manager.counter( - "codex.windows_sandbox.elevated_setup_failure", + codex_core::windows_sandbox::elevated_setup_failure_metric_name( + &err, + ), 1, &tags, ); diff --git a/codex-rs/windows-sandbox-rs/src/setup_error.rs b/codex-rs/windows-sandbox-rs/src/setup_error.rs index 21d260b4d4..9ee9ebf7f5 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_error.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_error.rs @@ -22,6 +22,8 @@ pub enum SetupErrorCode { OrchestratorPayloadSerializeFailed, /// Failed to launch the setup helper process (spawn or ShellExecuteExW). OrchestratorHelperLaunchFailed, + /// User canceled the UAC prompt while launching the helper. + OrchestratorHelperLaunchCanceled, /// Helper exited non-zero and no structured report was available. OrchestratorHelperExitNonzero, /// Helper exited non-zero and reading `setup_error.json` failed. @@ -72,6 +74,7 @@ impl SetupErrorCode { Self::OrchestratorElevationCheckFailed => "orchestrator_elevation_check_failed", Self::OrchestratorPayloadSerializeFailed => "orchestrator_payload_serialize_failed", Self::OrchestratorHelperLaunchFailed => "orchestrator_helper_launch_failed", + Self::OrchestratorHelperLaunchCanceled => "orchestrator_helper_launch_canceled", Self::OrchestratorHelperExitNonzero => "orchestrator_helper_exit_nonzero", Self::OrchestratorHelperReportReadFailed => "orchestrator_helper_report_read_failed", Self::HelperRequestArgsFailed => "helper_request_args_failed", diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 74137379ec..78e6d566fb 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -34,6 +34,7 @@ use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY; pub const SETUP_VERSION: u32 = 5; pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline"; pub const ONLINE_USERNAME: &str = "CodexSandboxOnline"; +const ERROR_CANCELLED: u32 = 1223; const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020; const DOMAIN_ALIAS_RID_ADMINS: u32 = 0x0000_0220; @@ -411,8 +412,13 @@ fn run_setup_exe( let ok = unsafe { ShellExecuteExW(&mut sei) }; if ok == 0 || sei.hProcess == 0 { let last_error = unsafe { GetLastError() }; + let code = if last_error == ERROR_CANCELLED { + SetupErrorCode::OrchestratorHelperLaunchCanceled + } else { + SetupErrorCode::OrchestratorHelperLaunchFailed + }; return Err(failure( - SetupErrorCode::OrchestratorHelperLaunchFailed, + code, format!("ShellExecuteExW failed to launch setup helper: {last_error}"), )); } From 98debeda8aa19336a063645b7414498ad56f17ee Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Mon, 2 Feb 2026 15:35:37 -0800 Subject: [PATCH 079/139] chore(tui) /personalities tip (#10377) ## Summary We have /personality now. ## Testing - [x] tested locally --- codex-rs/tui/tooltips.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/codex-rs/tui/tooltips.txt b/codex-rs/tui/tooltips.txt index a8521f359f..e1e4d23712 100644 --- a/codex-rs/tui/tooltips.txt +++ b/codex-rs/tui/tooltips.txt @@ -9,6 +9,7 @@ Use /status to see the current model, approvals, and token usage. Use /fork to branch the current chat into a new thread. Use /init to create an AGENTS.md with project-specific guidance. Use /mcp to list configured MCP tools. +Use /personality to customize how Codex communicates. Use /rename to rename your threads for easier thread resuming. Use the OpenAI docs MCP for API questions; enable it with `codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp`. Join the OpenAI community Discord: http://discord.gg/openai From fb2df99cf132c0980d1b54e1a8fb64a1f9ed8906 Mon Sep 17 00:00:00 2001 From: Celia Chen Date: Mon, 2 Feb 2026 16:06:44 -0800 Subject: [PATCH 080/139] [feat] persist thread_dynamic_tools in db (#10252) Persist thread_dynamic_tools in sqlite and read first from it. Fall back to rollout files if it's not found. Persist dynamic tools to both sqlite and rollout files. Saw that new sessions get populated to db correctly & old sessions get backfilled correctly at startup: ``` celia@com-92114 codex-rs % sqlite3 ~/.codex/state.sqlite \ "select thread_id, position,name,description,input_schema from thread_dynamic_tools;" 019c0cad-ec0d-74b2-a787-e8b33a349117|0|geo_lookup|lookup a city|{"properties":{"city":{"type":"string"}},"required":["city"],"type":"object"} .... 019c10ca-aa4b-7620-ae40-c0919fbd7ea7|0|geo_lookup|lookup a city|{"properties":{"city":{"type":"string"}},"required":["city"],"type":"object"} ``` --- codex-rs/app-server-test-client/src/main.rs | 117 ++++++++++++++++-- codex-rs/core/src/codex.rs | 31 ++++- codex-rs/core/src/rollout/metadata.rs | 23 ++++ codex-rs/core/src/state_db.rs | 47 +++++++ codex-rs/core/tests/suite/sqlite_state.rs | 37 +++++- .../migrations/0004_thread_dynamic_tools.sql | 11 ++ codex-rs/state/src/runtime.rs | 109 ++++++++++++++++ 7 files changed, 359 insertions(+), 16 deletions(-) create mode 100644 codex-rs/state/migrations/0004_thread_dynamic_tools.sql diff --git a/codex-rs/app-server-test-client/src/main.rs b/codex-rs/app-server-test-client/src/main.rs index d949c3a734..6d3fc06d81 100644 --- a/codex-rs/app-server-test-client/src/main.rs +++ b/codex-rs/app-server-test-client/src/main.rs @@ -1,7 +1,9 @@ use std::collections::VecDeque; +use std::fs; use std::io::BufRead; use std::io::BufReader; use std::io::Write; +use std::path::Path; use std::process::Child; use std::process::ChildStdin; use std::process::ChildStdout; @@ -24,6 +26,7 @@ use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::CommandExecutionApprovalDecision; use codex_app_server_protocol::CommandExecutionRequestApprovalParams; use codex_app_server_protocol::CommandExecutionRequestApprovalResponse; +use codex_app_server_protocol::DynamicToolSpec; use codex_app_server_protocol::FileChangeApprovalDecision; use codex_app_server_protocol::FileChangeRequestApprovalParams; use codex_app_server_protocol::FileChangeRequestApprovalResponse; @@ -83,6 +86,15 @@ struct Cli { )] config_overrides: Vec, + /// JSON array of dynamic tool specs or a single tool object. + /// Prefix a filename with '@' to read from a file. + /// + /// Example: + /// --dynamic-tools '[{"name":"demo","description":"Demo","inputSchema":{"type":"object"}}]' + /// --dynamic-tools @/path/to/tools.json + #[arg(long, value_name = "json-or-@file", global = true)] + dynamic_tools: Option, + #[command(subcommand)] command: CliCommand, } @@ -140,23 +152,29 @@ fn main() -> Result<()> { let Cli { codex_bin, config_overrides, + dynamic_tools, command, } = Cli::parse(); + let dynamic_tools = parse_dynamic_tools_arg(&dynamic_tools)?; + match command { CliCommand::SendMessage { user_message } => { + ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?; send_message(&codex_bin, &config_overrides, user_message) } CliCommand::SendMessageV2 { user_message } => { - send_message_v2(&codex_bin, &config_overrides, user_message) + send_message_v2(&codex_bin, &config_overrides, user_message, &dynamic_tools) } CliCommand::TriggerCmdApproval { user_message } => { - trigger_cmd_approval(&codex_bin, &config_overrides, user_message) + trigger_cmd_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) } CliCommand::TriggerPatchApproval { user_message } => { - trigger_patch_approval(&codex_bin, &config_overrides, user_message) + trigger_patch_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::NoTriggerCmdApproval => { + no_trigger_cmd_approval(&codex_bin, &config_overrides, &dynamic_tools) } - CliCommand::NoTriggerCmdApproval => no_trigger_cmd_approval(&codex_bin, &config_overrides), CliCommand::SendFollowUpV2 { first_message, follow_up_message, @@ -165,10 +183,20 @@ fn main() -> Result<()> { &config_overrides, first_message, follow_up_message, + &dynamic_tools, ), - CliCommand::TestLogin => test_login(&codex_bin, &config_overrides), - CliCommand::GetAccountRateLimits => get_account_rate_limits(&codex_bin, &config_overrides), - CliCommand::ModelList => model_list(&codex_bin, &config_overrides), + CliCommand::TestLogin => { + ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?; + test_login(&codex_bin, &config_overrides) + } + CliCommand::GetAccountRateLimits => { + ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?; + get_account_rate_limits(&codex_bin, &config_overrides) + } + CliCommand::ModelList => { + ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?; + model_list(&codex_bin, &config_overrides) + } } } @@ -198,14 +226,23 @@ fn send_message_v2( codex_bin: &str, config_overrides: &[String], user_message: String, + dynamic_tools: &Option>, ) -> Result<()> { - send_message_v2_with_policies(codex_bin, config_overrides, user_message, None, None) + send_message_v2_with_policies( + codex_bin, + config_overrides, + user_message, + None, + None, + dynamic_tools, + ) } fn trigger_cmd_approval( codex_bin: &str, config_overrides: &[String], user_message: Option, + dynamic_tools: &Option>, ) -> Result<()> { let default_prompt = "Run `touch /tmp/should-trigger-approval` so I can confirm the file exists."; @@ -216,6 +253,7 @@ fn trigger_cmd_approval( message, Some(AskForApproval::OnRequest), Some(SandboxPolicy::ReadOnly), + dynamic_tools, ) } @@ -223,6 +261,7 @@ fn trigger_patch_approval( codex_bin: &str, config_overrides: &[String], user_message: Option, + dynamic_tools: &Option>, ) -> Result<()> { let default_prompt = "Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch."; @@ -233,12 +272,24 @@ fn trigger_patch_approval( message, Some(AskForApproval::OnRequest), Some(SandboxPolicy::ReadOnly), + dynamic_tools, ) } -fn no_trigger_cmd_approval(codex_bin: &str, config_overrides: &[String]) -> Result<()> { +fn no_trigger_cmd_approval( + codex_bin: &str, + config_overrides: &[String], + dynamic_tools: &Option>, +) -> Result<()> { let prompt = "Run `touch should_not_trigger_approval.txt`"; - send_message_v2_with_policies(codex_bin, config_overrides, prompt.to_string(), None, None) + send_message_v2_with_policies( + codex_bin, + config_overrides, + prompt.to_string(), + None, + None, + dynamic_tools, + ) } fn send_message_v2_with_policies( @@ -247,13 +298,17 @@ fn send_message_v2_with_policies( user_message: String, approval_policy: Option, sandbox_policy: Option, + dynamic_tools: &Option>, ) -> Result<()> { let mut client = CodexClient::spawn(codex_bin, config_overrides)?; let initialize = client.initialize()?; println!("< initialize response: {initialize:?}"); - let thread_response = client.thread_start(ThreadStartParams::default())?; + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; println!("< thread/start response: {thread_response:?}"); let mut turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), @@ -280,13 +335,17 @@ fn send_follow_up_v2( config_overrides: &[String], first_message: String, follow_up_message: String, + dynamic_tools: &Option>, ) -> Result<()> { let mut client = CodexClient::spawn(codex_bin, config_overrides)?; let initialize = client.initialize()?; println!("< initialize response: {initialize:?}"); - let thread_response = client.thread_start(ThreadStartParams::default())?; + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; println!("< thread/start response: {thread_response:?}"); let first_turn_params = TurnStartParams { @@ -372,6 +431,40 @@ fn model_list(codex_bin: &str, config_overrides: &[String]) -> Result<()> { Ok(()) } +fn ensure_dynamic_tools_unused( + dynamic_tools: &Option>, + command: &str, +) -> Result<()> { + if dynamic_tools.is_some() { + bail!( + "dynamic tools are only supported for v2 thread/start; remove --dynamic-tools for {command} or use send-message-v2" + ); + } + Ok(()) +} + +fn parse_dynamic_tools_arg(dynamic_tools: &Option) -> Result>> { + let Some(raw_arg) = dynamic_tools.as_deref() else { + return Ok(None); + }; + + let raw_json = if let Some(path) = raw_arg.strip_prefix('@') { + fs::read_to_string(Path::new(path)) + .with_context(|| format!("read dynamic tools file {path}"))? + } else { + raw_arg.to_string() + }; + + let value: Value = serde_json::from_str(&raw_json).context("parse dynamic tools JSON")?; + let tools = match value { + Value::Array(_) => serde_json::from_value(value).context("decode dynamic tools array")?, + Value::Object(_) => vec![serde_json::from_value(value).context("decode dynamic tool")?], + _ => bail!("dynamic tools JSON must be an object or array"), + }; + + Ok(Some(tools)) +} + struct CodexClient { child: Child, stdin: Option, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ba5ee02d69..b4e3859500 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -333,9 +333,36 @@ impl Codex { .clone() .or_else(|| conversation_history.get_base_instructions().map(|s| s.text)) .unwrap_or_else(|| model_info.get_model_instructions(config.personality)); - // Respect explicit thread-start tools; fall back to persisted tools when resuming a thread. + + // Respect thread-start tools. When missing (resumed/forked threads), read from the db + // first, then fall back to rollout-file tools. + let persisted_tools = if dynamic_tools.is_empty() + && config.features.enabled(Feature::Sqlite) + { + let thread_id = match &conversation_history { + InitialHistory::Resumed(resumed) => Some(resumed.conversation_id), + InitialHistory::Forked(_) => conversation_history.forked_from_id(), + InitialHistory::New => None, + }; + match thread_id { + Some(thread_id) => { + let state_db_ctx = state_db::open_if_present( + config.codex_home.as_path(), + config.model_provider_id.as_str(), + ) + .await; + state_db::get_dynamic_tools(state_db_ctx.as_deref(), thread_id, "codex_spawn") + .await + } + None => None, + } + } else { + None + }; let dynamic_tools = if dynamic_tools.is_empty() { - conversation_history.get_dynamic_tools().unwrap_or_default() + persisted_tools + .or_else(|| conversation_history.get_dynamic_tools()) + .unwrap_or_default() } else { dynamic_tools }; diff --git a/codex-rs/core/src/rollout/metadata.rs b/codex-rs/core/src/rollout/metadata.rs index 2d59d71af3..42e52f78d6 100644 --- a/codex-rs/core/src/rollout/metadata.rs +++ b/codex-rs/core/src/rollout/metadata.rs @@ -187,6 +187,29 @@ pub(crate) async fn backfill_sessions( warn!("failed to upsert rollout {}: {err}", path.display()); } else { stats.upserted = stats.upserted.saturating_add(1); + if let Ok(meta_line) = rollout::list::read_session_meta_line(&path).await { + if let Err(err) = runtime + .persist_dynamic_tools( + meta_line.meta.id, + meta_line.meta.dynamic_tools.as_deref(), + ) + .await + { + if let Some(otel) = otel { + otel.counter( + DB_ERROR_METRIC, + 1, + &[("stage", "backfill_dynamic_tools")], + ); + } + warn!("failed to backfill dynamic tools {}: {err}", path.display()); + } + } else { + warn!( + "failed to read session meta for dynamic tools {}", + path.display() + ); + } } } Err(err) => { diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index a52f46593a..ff95ed946f 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -9,6 +9,7 @@ use chrono::Timelike; use chrono::Utc; use codex_otel::OtelManager; use codex_protocol::ThreadId; +use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionSource; use codex_state::DB_METRIC_COMPARE_ERROR; @@ -196,6 +197,37 @@ pub async fn find_rollout_path_by_id( }) } +/// Get dynamic tools for a thread id using SQLite. +pub async fn get_dynamic_tools( + context: Option<&codex_state::StateRuntime>, + thread_id: ThreadId, + stage: &str, +) -> Option> { + let ctx = context?; + match ctx.get_dynamic_tools(thread_id).await { + Ok(tools) => tools, + Err(err) => { + warn!("state db get_dynamic_tools failed during {stage}: {err}"); + None + } + } +} + +/// Persist dynamic tools for a thread id using SQLite, if none exist yet. +pub async fn persist_dynamic_tools( + context: Option<&codex_state::StateRuntime>, + thread_id: ThreadId, + tools: Option<&[DynamicToolSpec]>, + stage: &str, +) { + let Some(ctx) = context else { + return; + }; + if let Err(err) = ctx.persist_dynamic_tools(thread_id, tools).await { + warn!("state db persist_dynamic_tools failed during {stage}: {err}"); + } +} + /// Reconcile rollout items into SQLite, falling back to scanning the rollout file. pub async fn reconcile_rollout( context: Option<&codex_state::StateRuntime>, @@ -235,6 +267,21 @@ pub async fn reconcile_rollout( "state db reconcile_rollout upsert failed {}: {err}", rollout_path.display() ); + return; + } + if let Ok(meta_line) = crate::rollout::list::read_session_meta_line(rollout_path).await { + persist_dynamic_tools( + Some(ctx), + meta_line.meta.id, + meta_line.meta.dynamic_tools.as_deref(), + "reconcile_rollout", + ) + .await; + } else { + warn!( + "state db reconcile_rollout missing session meta {}", + rollout_path.display() + ); } } diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 2582f90cbe..218da34825 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -1,6 +1,7 @@ use anyhow::Result; use codex_core::features::Feature; use codex_protocol::ThreadId; +use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::RolloutLine; @@ -74,6 +75,28 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { let rollout_rel_path = format!("sessions/2026/01/27/rollout-2026-01-27T12-00-00-{uuid}.jsonl"); let rollout_rel_path_for_hook = rollout_rel_path.clone(); + let dynamic_tools = vec![ + DynamicToolSpec { + name: "geo_lookup".to_string(), + description: "lookup a city".to_string(), + input_schema: json!({ + "type": "object", + "required": ["city"], + "properties": { "city": { "type": "string" } } + }), + }, + DynamicToolSpec { + name: "weather_lookup".to_string(), + description: "lookup weather".to_string(), + input_schema: json!({ + "type": "object", + "required": ["zip"], + "properties": { "zip": { "type": "string" } } + }), + }, + ]; + let dynamic_tools_for_hook = dynamic_tools.clone(); + let mut builder = test_codex() .with_pre_build_hook(move |codex_home| { let rollout_path = codex_home.join(&rollout_rel_path_for_hook); @@ -81,7 +104,6 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { .parent() .expect("rollout path should have parent"); fs::create_dir_all(parent).expect("should create rollout directory"); - let session_meta_line = SessionMetaLine { meta: SessionMeta { id: thread_id, @@ -93,7 +115,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { source: SessionSource::default(), model_provider: None, base_instructions: None, - dynamic_tools: None, + dynamic_tools: Some(dynamic_tools_for_hook), }, git: None, }; @@ -155,6 +177,17 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { assert_eq!(metadata.model_provider, default_provider); assert!(metadata.has_user_event); + let mut stored_tools = None; + for _ in 0..40 { + stored_tools = db.get_dynamic_tools(thread_id).await?; + if stored_tools.is_some() { + break; + } + tokio::time::sleep(Duration::from_millis(25)).await; + } + let stored_tools = stored_tools.expect("dynamic tools should be stored"); + assert_eq!(stored_tools, dynamic_tools); + Ok(()) } diff --git a/codex-rs/state/migrations/0004_thread_dynamic_tools.sql b/codex-rs/state/migrations/0004_thread_dynamic_tools.sql new file mode 100644 index 0000000000..0f40b5f800 --- /dev/null +++ b/codex-rs/state/migrations/0004_thread_dynamic_tools.sql @@ -0,0 +1,11 @@ +CREATE TABLE thread_dynamic_tools ( + thread_id TEXT NOT NULL, + position INTEGER NOT NULL, + name TEXT NOT NULL, + description TEXT NOT NULL, + input_schema TEXT NOT NULL, + PRIMARY KEY(thread_id, position), + FOREIGN KEY(thread_id) REFERENCES threads(id) ON DELETE CASCADE +); + +CREATE INDEX idx_thread_dynamic_tools_thread ON thread_dynamic_tools(thread_id); diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 6f2e128967..3b37b6d424 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -16,8 +16,10 @@ use chrono::DateTime; use chrono::Utc; use codex_otel::OtelManager; use codex_protocol::ThreadId; +use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::protocol::RolloutItem; use log::LevelFilter; +use serde_json::Value; use sqlx::ConnectOptions; use sqlx::QueryBuilder; use sqlx::Row; @@ -117,6 +119,38 @@ WHERE id = ? .transpose() } + /// Get dynamic tools for a thread, if present. + pub async fn get_dynamic_tools( + &self, + thread_id: ThreadId, + ) -> anyhow::Result>> { + let rows = sqlx::query( + r#" +SELECT name, description, input_schema +FROM thread_dynamic_tools +WHERE thread_id = ? +ORDER BY position ASC + "#, + ) + .bind(thread_id.to_string()) + .fetch_all(self.pool.as_ref()) + .await?; + if rows.is_empty() { + return Ok(None); + } + let mut tools = Vec::with_capacity(rows.len()); + for row in rows { + let input_schema: String = row.try_get("input_schema")?; + let input_schema = serde_json::from_str::(input_schema.as_str())?; + tools.push(DynamicToolSpec { + name: row.try_get("name")?, + description: row.try_get("description")?, + input_schema, + }); + } + Ok(Some(tools)) + } + /// Find a rollout path by thread id using the underlying database. pub async fn find_rollout_path_by_id( &self, @@ -369,6 +403,58 @@ ON CONFLICT(id) DO UPDATE SET Ok(()) } + /// Persist dynamic tools for a thread if none have been stored yet. + /// + /// Dynamic tools are defined at thread start and should not change afterward. + /// This only writes the first time we see tools for a given thread. + pub async fn persist_dynamic_tools( + &self, + thread_id: ThreadId, + tools: Option<&[DynamicToolSpec]>, + ) -> anyhow::Result<()> { + let Some(tools) = tools else { + return Ok(()); + }; + if tools.is_empty() { + return Ok(()); + } + let mut tx = self.pool.begin().await?; + let thread_id = thread_id.to_string(); + let existing: Option = + sqlx::query_scalar("SELECT 1 FROM thread_dynamic_tools WHERE thread_id = ? LIMIT 1") + .bind(thread_id.as_str()) + .fetch_optional(&mut *tx) + .await?; + if existing.is_some() { + tx.commit().await?; + return Ok(()); + } + for (idx, tool) in tools.iter().enumerate() { + let position = i64::try_from(idx).unwrap_or(i64::MAX); + let input_schema = serde_json::to_string(&tool.input_schema)?; + sqlx::query( + r#" +INSERT INTO thread_dynamic_tools ( + thread_id, + position, + name, + description, + input_schema +) VALUES (?, ?, ?, ?, ?) + "#, + ) + .bind(thread_id.as_str()) + .bind(position) + .bind(tool.name.as_str()) + .bind(tool.description.as_str()) + .bind(input_schema) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + Ok(()) + } + /// Apply rollout items incrementally using the underlying database. pub async fn apply_rollout_items( &self, @@ -390,12 +476,25 @@ ON CONFLICT(id) DO UPDATE SET if let Some(updated_at) = file_modified_time_utc(builder.rollout_path.as_path()).await { metadata.updated_at = updated_at; } + // Keep the thread upsert before dynamic tools to satisfy the foreign key constraint: + // thread_dynamic_tools.thread_id -> threads.id. if let Err(err) = self.upsert_thread(&metadata).await { if let Some(otel) = otel { otel.counter(DB_ERROR_METRIC, 1, &[("stage", "apply_rollout_items")]); } return Err(err); } + let dynamic_tools = extract_dynamic_tools(items); + if let Some(dynamic_tools) = dynamic_tools + && let Err(err) = self + .persist_dynamic_tools(builder.id, dynamic_tools.as_deref()) + .await + { + if let Some(otel) = otel { + otel.counter(DB_ERROR_METRIC, 1, &[("stage", "persist_dynamic_tools")]); + } + return Err(err); + } Ok(()) } @@ -507,6 +606,16 @@ fn push_like_filters<'a>( builder.push(")"); } +fn extract_dynamic_tools(items: &[RolloutItem]) -> Option>> { + items.iter().find_map(|item| match item { + RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.dynamic_tools.clone()), + RolloutItem::ResponseItem(_) + | RolloutItem::Compacted(_) + | RolloutItem::TurnContext(_) + | RolloutItem::EventMsg(_) => None, + }) +} + async fn open_sqlite(path: &Path) -> anyhow::Result { let options = SqliteConnectOptions::new() .filename(path) From e24058b7a872054e3e76f9301d64eccccb75811d Mon Sep 17 00:00:00 2001 From: Gav Verma Date: Mon, 2 Feb 2026 16:49:23 -0800 Subject: [PATCH 081/139] feat: Read personal skills from .agents/skills (#10437) - Issue: https://github.com/agentskills/agentskills/issues/15 - Follow-up to https://github.com/openai/codex/pull/10317 (for team/repo skills) - This change now also loads personal/user skills from `$HOME/.agents/skills` (or `~/.agents/skills`) in addition to loading from `.agents/skills` inside of git repos. - The location of `.system` skills remains unchanged. - Keeping backwards compatibility with `~/.codex/skills` for now until we fully deprecate. With skills in both personal folders: image We load from both places: image --- codex-rs/Cargo.lock | 1 + codex-rs/core/Cargo.toml | 1 + codex-rs/core/src/skills/loader.rs | 92 +++++++++++++++++++++++++++--- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 57186f82f1..716bc4cd5a 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1422,6 +1422,7 @@ dependencies = [ "core-foundation 0.9.4", "core_test_support", "ctor 0.6.3", + "dirs", "dunce", "encoding_rs", "env-flags", diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index a857da8b3a..a95981cca2 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -45,6 +45,7 @@ codex-utils-pty = { workspace = true } codex-utils-readiness = { workspace = true } codex-utils-string = { workspace = true } codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } +dirs = { workspace = true } dunce = { workspace = true } encoding_rs = { workspace = true } env-flags = { workspace = true } diff --git a/codex-rs/core/src/skills/loader.rs b/codex-rs/core/src/skills/loader.rs index a3c7731a6e..a04f3089b0 100644 --- a/codex-rs/core/src/skills/loader.rs +++ b/codex-rs/core/src/skills/loader.rs @@ -13,6 +13,7 @@ use crate::skills::model::SkillToolDependency; use crate::skills::system::system_cache_root_dir; use codex_app_server_protocol::ConfigLayerSource; use codex_protocol::protocol::SkillScope; +use dirs::home_dir; use dunce::canonicalize as canonicalize_path; use serde::Deserialize; use std::collections::HashSet; @@ -164,7 +165,10 @@ where outcome } -fn skill_roots_from_layer_stack_inner(config_layer_stack: &ConfigLayerStack) -> Vec { +fn skill_roots_from_layer_stack_inner( + config_layer_stack: &ConfigLayerStack, + home_dir: Option<&Path>, +) -> Vec { let mut roots = Vec::new(); for layer in @@ -182,12 +186,21 @@ fn skill_roots_from_layer_stack_inner(config_layer_stack: &ConfigLayerStack) -> }); } ConfigLayerSource::User { .. } => { - // `$CODEX_HOME/skills` (user-installed skills). + // Deprecated user skills location (`$CODEX_HOME/skills`), kept for backward + // compatibility. roots.push(SkillRoot { path: config_folder.as_path().join(SKILLS_DIR_NAME), scope: SkillScope::User, }); + // `$HOME/.agents/skills` (user-installed skills). + if let Some(home_dir) = home_dir { + roots.push(SkillRoot { + path: home_dir.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME), + scope: SkillScope::User, + }); + } + // Embedded system skills are cached under `$CODEX_HOME/skills/.system` and are a // special case (not a config layer). roots.push(SkillRoot { @@ -220,15 +233,16 @@ fn skill_roots(config: &Config) -> Vec { #[cfg(test)] pub(crate) fn skill_roots_from_layer_stack( config_layer_stack: &ConfigLayerStack, + home_dir: Option<&Path>, ) -> Vec { - skill_roots_from_layer_stack_inner(config_layer_stack) + skill_roots_from_layer_stack_inner(config_layer_stack, home_dir) } pub(crate) fn skill_roots_from_layer_stack_with_agents( config_layer_stack: &ConfigLayerStack, cwd: &Path, ) -> Vec { - let mut roots = skill_roots_from_layer_stack_inner(config_layer_stack); + let mut roots = skill_roots_from_layer_stack_inner(config_layer_stack, home_dir().as_deref()); roots.extend(repo_agents_skill_roots(config_layer_stack, cwd)); dedupe_skill_roots_by_path(&mut roots); roots @@ -829,7 +843,8 @@ mod tests { let tmp = tempfile::tempdir()?; let system_folder = tmp.path().join("etc/codex"); - let user_folder = tmp.path().join("home/codex"); + let home_folder = tmp.path().join("home"); + let user_folder = home_folder.join("codex"); fs::create_dir_all(&system_folder)?; fs::create_dir_all(&user_folder)?; @@ -853,7 +868,7 @@ mod tests { ConfigRequirementsToml::default(), )?; - let got = skill_roots_from_layer_stack(&stack) + let got = skill_roots_from_layer_stack(&stack, Some(&home_folder)) .into_iter() .map(|root| (root.scope, root.path)) .collect::>(); @@ -862,6 +877,10 @@ mod tests { got, vec![ (SkillScope::User, user_folder.join("skills")), + ( + SkillScope::User, + home_folder.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME) + ), ( SkillScope::System, user_folder.join("skills").join(".system") @@ -877,7 +896,8 @@ mod tests { fn skill_roots_from_layer_stack_includes_disabled_project_layers() -> anyhow::Result<()> { let tmp = tempfile::tempdir()?; - let user_folder = tmp.path().join("home/codex"); + let home_folder = tmp.path().join("home"); + let user_folder = home_folder.join("codex"); fs::create_dir_all(&user_folder)?; let project_root = tmp.path().join("repo"); @@ -906,7 +926,7 @@ mod tests { ConfigRequirementsToml::default(), )?; - let got = skill_roots_from_layer_stack(&stack) + let got = skill_roots_from_layer_stack(&stack, Some(&home_folder)) .into_iter() .map(|root| (root.scope, root.path)) .collect::>(); @@ -916,6 +936,10 @@ mod tests { vec![ (SkillScope::Repo, dot_codex.join("skills")), (SkillScope::User, user_folder.join("skills")), + ( + SkillScope::User, + home_folder.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME) + ), ( SkillScope::System, user_folder.join("skills").join(".system") @@ -926,6 +950,55 @@ mod tests { Ok(()) } + #[test] + fn loads_skills_from_home_agents_dir_for_user_scope() -> anyhow::Result<()> { + let tmp = tempfile::tempdir()?; + + let home_folder = tmp.path().join("home"); + let user_folder = home_folder.join("codex"); + fs::create_dir_all(&user_folder)?; + + let user_file = AbsolutePathBuf::from_absolute_path(user_folder.join("config.toml"))?; + let layers = vec![ConfigLayerEntry::new( + ConfigLayerSource::User { file: user_file }, + TomlValue::Table(toml::map::Map::new()), + )]; + let stack = ConfigLayerStack::new( + layers, + ConfigRequirements::default(), + ConfigRequirementsToml::default(), + )?; + + let skill_path = write_skill_at( + &home_folder.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME), + "agents-home", + "agents-home-skill", + "from home agents", + ); + + let outcome = + load_skills_from_roots(skill_roots_from_layer_stack(&stack, Some(&home_folder))); + assert!( + outcome.errors.is_empty(), + "unexpected errors: {:?}", + outcome.errors + ); + assert_eq!( + outcome.skills, + vec![SkillMetadata { + name: "agents-home-skill".to_string(), + description: "from home agents".to_string(), + short_description: None, + interface: None, + dependencies: None, + path: normalized(&skill_path), + scope: SkillScope::User, + }] + ); + + Ok(()) + } + fn write_skill(codex_home: &TempDir, dir: &str, name: &str, description: &str) -> PathBuf { write_skill_at(&codex_home.path().join("skills"), dir, name, description) } @@ -2136,6 +2209,9 @@ interface: .map(|root| root.scope) .collect(); let mut expected = vec![SkillScope::User, SkillScope::System]; + if home_dir().is_some() { + expected.insert(1, SkillScope::User); + } if cfg!(unix) { expected.push(SkillScope::Admin); } From 019d89ff86587196341edc30c9bb6205f82537ce Mon Sep 17 00:00:00 2001 From: pash-openai Date: Mon, 2 Feb 2026 16:57:29 -0800 Subject: [PATCH 082/139] make codex better at git (#10145) adds basic git context to the session prefix so the model can anchor git actions and be a bit more version-aware. structured it in a multiroot-friendly shape even though we only have one root today --- codex-rs/core/src/client.rs | 66 +++++- codex-rs/core/src/codex.rs | 8 +- codex-rs/core/src/compact.rs | 4 +- codex-rs/core/src/environment_context.rs | 1 + codex-rs/core/src/git_info.rs | 76 ++++++- codex-rs/core/src/lib.rs | 2 + codex-rs/core/src/turn_metadata.rs | 43 ++++ .../core/tests/chat_completions_payload.rs | 2 +- codex-rs/core/tests/chat_completions_sse.rs | 2 +- codex-rs/core/tests/responses_headers.rs | 201 +++++++++++++++++- codex-rs/core/tests/suite/client.rs | 2 +- .../core/tests/suite/client_websockets.rs | 10 +- 12 files changed, 394 insertions(+), 23 deletions(-) create mode 100644 codex-rs/core/src/turn_metadata.rs diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 6564e86c49..37fef163af 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -1,10 +1,13 @@ +use std::path::PathBuf; use std::sync::Arc; use std::sync::OnceLock; +use std::sync::RwLock; use crate::api_bridge::CoreAuthProvider; use crate::api_bridge::auth_provider_from_auth; use crate::api_bridge::map_api_error; use crate::auth::UnauthorizedRecovery; +use crate::turn_metadata::build_turn_metadata_header; use codex_api::AggregateStreamExt; use codex_api::ChatClient as ApiChatClient; use codex_api::CompactClient as ApiCompactClient; @@ -72,6 +75,13 @@ use crate::transport_manager::TransportManager; pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; +pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; + +#[derive(Debug, Default)] +struct TurnMetadataCache { + cwd: Option, + header: Option, +} #[derive(Debug)] struct ModelClientState { @@ -85,6 +95,7 @@ struct ModelClientState { summary: ReasoningSummaryConfig, session_source: SessionSource, transport_manager: TransportManager, + turn_metadata_cache: Arc>, } #[derive(Debug, Clone)] @@ -136,11 +147,13 @@ impl ModelClient { summary, session_source, transport_manager, + turn_metadata_cache: Arc::new(RwLock::new(TurnMetadataCache::default())), }), } } - pub fn new_session(&self) -> ModelClientSession { + pub fn new_session(&self, turn_metadata_cwd: Option) -> ModelClientSession { + self.prewarm_turn_metadata_header(turn_metadata_cwd); ModelClientSession { state: Arc::clone(&self.state), connection: None, @@ -149,6 +162,38 @@ impl ModelClient { turn_state: Arc::new(OnceLock::new()), } } + + /// Refresh turn metadata in the background and update a cached header that request + /// builders can read without blocking. + fn prewarm_turn_metadata_header(&self, turn_metadata_cwd: Option) { + let turn_metadata_cwd = + turn_metadata_cwd.map(|cwd| std::fs::canonicalize(&cwd).unwrap_or(cwd)); + + if let Ok(mut cache) = self.state.turn_metadata_cache.write() + && cache.cwd != turn_metadata_cwd + { + cache.cwd = turn_metadata_cwd.clone(); + cache.header = None; + } + + let Some(cwd) = turn_metadata_cwd else { + return; + }; + let turn_metadata_cache = Arc::clone(&self.state.turn_metadata_cache); + if let Ok(handle) = tokio::runtime::Handle::try_current() { + let _task = handle.spawn(async move { + let header = build_turn_metadata_header(cwd.as_path()) + .await + .and_then(|value| HeaderValue::from_str(value.as_str()).ok()); + + if let Ok(mut cache) = turn_metadata_cache.write() + && cache.cwd.as_ref() == Some(&cwd) + { + cache.header = header; + } + }); + } + } } impl ModelClient { @@ -257,6 +302,14 @@ impl ModelClient { } impl ModelClientSession { + fn turn_metadata_header(&self) -> Option { + self.state + .turn_metadata_cache + .try_read() + .ok() + .and_then(|cache| cache.header.clone()) + } + /// Streams a single model turn using either the Responses or Chat /// Completions wire API, depending on the configured provider. /// @@ -332,6 +385,7 @@ impl ModelClientSession { prompt: &Prompt, compression: Compression, ) -> ApiResponsesOptions { + let turn_metadata_header = self.turn_metadata_header(); let model_info = &self.state.model_info; let default_reasoning_effort = model_info.default_reasoning_level; @@ -380,7 +434,11 @@ impl ModelClientSession { store_override: None, conversation_id: Some(conversation_id), session_source: Some(self.state.session_source.clone()), - extra_headers: build_responses_headers(&self.state.config, Some(&self.turn_state)), + extra_headers: build_responses_headers( + &self.state.config, + Some(&self.turn_state), + turn_metadata_header.as_ref(), + ), compression, turn_state: Some(Arc::clone(&self.turn_state)), } @@ -713,6 +771,7 @@ fn experimental_feature_headers(config: &Config) -> ApiHeaderMap { fn build_responses_headers( config: &Config, turn_state: Option<&Arc>>, + turn_metadata_header: Option<&HeaderValue>, ) -> ApiHeaderMap { let mut headers = experimental_feature_headers(config); headers.insert( @@ -731,6 +790,9 @@ fn build_responses_headers( { headers.insert(X_CODEX_TURN_STATE_HEADER, header_value); } + if let Some(header_value) = turn_metadata_header { + headers.insert(X_CODEX_TURN_METADATA_HEADER, header_value.clone()); + } headers } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index b4e3859500..31a81846e2 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -119,6 +119,7 @@ use crate::error::Result as CodexResult; use crate::exec::StreamOutput; use crate::exec_policy::ExecPolicyUpdateError; use crate::feedback_tags; +use crate::git_info::get_git_repo_root; use crate::instructions::UserInstructions; use crate::mcp::CODEX_APPS_MCP_SERVER_NAME; use crate::mcp::auth::compute_auth_statuses; @@ -531,7 +532,6 @@ pub(crate) struct TurnContext { pub(crate) truncation_policy: TruncationPolicy, pub(crate) dynamic_tools: Vec, } - impl TurnContext { pub(crate) fn resolve_path(&self, path: Option) -> PathBuf { path.as_ref() @@ -3406,7 +3406,9 @@ pub(crate) async fn run_turn( // many turns, from the perspective of the user, it is a single turn. let turn_diff_tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); - let mut client_session = turn_context.client.new_session(); + let mut client_session = turn_context + .client + .new_session(Some(turn_context.cwd.clone())); loop { // Note that pending_input would be something like a message the user @@ -4469,8 +4471,6 @@ pub(super) fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) - #[cfg(test)] pub(crate) use tests::make_session_and_context; - -use crate::git_info::get_git_repo_root; #[cfg(test)] pub(crate) use tests::make_session_and_context_with_rx; diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 7e7445a6f6..b44d43f42e 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -335,7 +335,9 @@ async fn drain_to_completed( turn_context: &TurnContext, prompt: &Prompt, ) -> CodexResult<()> { - let mut client_session = turn_context.client.new_session(); + let mut client_session = turn_context + .client + .new_session(Some(turn_context.cwd.clone())); let mut stream = client_session.stream(prompt).await?; loop { let maybe_event = stream.next().await; diff --git a/codex-rs/core/src/environment_context.rs b/codex-rs/core/src/environment_context.rs index f0b0877eba..87692a2f05 100644 --- a/codex-rs/core/src/environment_context.rs +++ b/codex-rs/core/src/environment_context.rs @@ -28,6 +28,7 @@ impl EnvironmentContext { cwd, // should compare all fields except shell shell: _, + .. } = other; self.cwd == *cwd diff --git a/codex-rs/core/src/git_info.rs b/codex-rs/core/src/git_info.rs index 065f1280a4..dcabc3a341 100644 --- a/codex-rs/core/src/git_info.rs +++ b/codex-rs/core/src/git_info.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; @@ -109,6 +110,73 @@ pub async fn collect_git_info(cwd: &Path) -> Option { Some(git_info) } +/// Collect fetch remotes in a multi-root-friendly format: {"origin": "https://..."}. +pub async fn get_git_remote_urls(cwd: &Path) -> Option> { + let is_git_repo = run_git_command_with_timeout(&["rev-parse", "--git-dir"], cwd) + .await? + .status + .success(); + if !is_git_repo { + return None; + } + + get_git_remote_urls_assume_git_repo(cwd).await +} + +/// Collect fetch remotes without checking whether `cwd` is in a git repo. +pub async fn get_git_remote_urls_assume_git_repo(cwd: &Path) -> Option> { + let output = run_git_command_with_timeout(&["remote", "-v"], cwd).await?; + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8(output.stdout).ok()?; + parse_git_remote_urls(stdout.as_str()) +} + +/// Return the current HEAD commit hash without checking whether `cwd` is in a git repo. +pub async fn get_head_commit_hash(cwd: &Path) -> Option { + let output = run_git_command_with_timeout(&["rev-parse", "HEAD"], cwd).await?; + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8(output.stdout).ok()?; + let hash = stdout.trim(); + if hash.is_empty() { + None + } else { + Some(hash.to_string()) + } +} + +fn parse_git_remote_urls(stdout: &str) -> Option> { + let mut remotes = BTreeMap::new(); + for line in stdout.lines() { + let Some(fetch_line) = line.strip_suffix(" (fetch)") else { + continue; + }; + + let Some((name, url_part)) = fetch_line + .split_once('\t') + .or_else(|| fetch_line.split_once(' ')) + else { + continue; + }; + + let url = url_part.trim_start(); + if !url.is_empty() { + remotes.insert(name.to_string(), url.to_string()); + } + } + + if remotes.is_empty() { + None + } else { + Some(remotes) + } +} + /// A minimal commit summary entry used for pickers (subject + timestamp + sha). #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CommitLogEntry { @@ -185,11 +253,9 @@ pub async fn git_diff_to_remote(cwd: &Path) -> Option { /// Run a git command with a timeout to prevent blocking on large repositories async fn run_git_command_with_timeout(args: &[&str], cwd: &Path) -> Option { - let result = timeout( - GIT_COMMAND_TIMEOUT, - Command::new("git").args(args).current_dir(cwd).output(), - ) - .await; + let mut command = Command::new("git"); + command.args(args).current_dir(cwd).kill_on_drop(true); + let result = timeout(GIT_COMMAND_TIMEOUT, command.output()).await; match result { Ok(Ok(output)) => Some(output), diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index e11b81e6fc..1f81f3eab2 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -101,6 +101,7 @@ pub mod state_db; pub mod terminal; mod tools; pub mod turn_diff_tracker; +mod turn_metadata; pub use rollout::ARCHIVED_SESSIONS_SUBDIR; pub use rollout::INTERACTIVE_SESSION_SOURCES; pub use rollout::RolloutRecorder; @@ -131,6 +132,7 @@ pub mod util; pub use apply_patch::CODEX_APPLY_PATCH_ARG1; pub use client::WEB_SEARCH_ELIGIBLE_HEADER; +pub use client::X_CODEX_TURN_METADATA_HEADER; pub use command_safety::is_dangerous_command; pub use command_safety::is_safe_command; pub use exec_policy::ExecPolicyError; diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs new file mode 100644 index 0000000000..58b394a2f2 --- /dev/null +++ b/codex-rs/core/src/turn_metadata.rs @@ -0,0 +1,43 @@ +use std::collections::BTreeMap; +use std::path::Path; + +use serde::Serialize; + +use crate::git_info::get_git_remote_urls_assume_git_repo; +use crate::git_info::get_git_repo_root; +use crate::git_info::get_head_commit_hash; + +#[derive(Serialize)] +struct TurnMetadataWorkspace { + #[serde(skip_serializing_if = "Option::is_none")] + associated_remote_urls: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + latest_git_commit_hash: Option, +} + +#[derive(Serialize)] +struct TurnMetadata { + workspaces: BTreeMap, +} + +pub(crate) async fn build_turn_metadata_header(cwd: &Path) -> Option { + let repo_root = get_git_repo_root(cwd)?; + + let (latest_git_commit_hash, associated_remote_urls) = tokio::join!( + get_head_commit_hash(cwd), + get_git_remote_urls_assume_git_repo(cwd) + ); + if latest_git_commit_hash.is_none() && associated_remote_urls.is_none() { + return None; + } + + let mut workspaces = BTreeMap::new(); + workspaces.insert( + repo_root.to_string_lossy().into_owned(), + TurnMetadataWorkspace { + associated_remote_urls, + latest_git_commit_hash, + }, + ); + serde_json::to_string(&TurnMetadata { workspaces }).ok() +} diff --git a/codex-rs/core/tests/chat_completions_payload.rs b/codex-rs/core/tests/chat_completions_payload.rs index ffa2caa59f..5406b44ad6 100644 --- a/codex-rs/core/tests/chat_completions_payload.rs +++ b/codex-rs/core/tests/chat_completions_payload.rs @@ -102,7 +102,7 @@ async fn run_request(input: Vec) -> Value { SessionSource::Exec, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = input; diff --git a/codex-rs/core/tests/chat_completions_sse.rs b/codex-rs/core/tests/chat_completions_sse.rs index 160699733e..8360237752 100644 --- a/codex-rs/core/tests/chat_completions_sse.rs +++ b/codex-rs/core/tests/chat_completions_sse.rs @@ -103,7 +103,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec { SessionSource::Exec, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 86d6e8ce61..a40c92d02d 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -1,3 +1,4 @@ +use std::process::Command; use std::sync::Arc; use codex_app_server_protocol::AuthMode; @@ -23,6 +24,7 @@ use core_test_support::load_default_config_for_test; use core_test_support::responses; use core_test_support::test_codex::test_codex; use futures::StreamExt; +use pretty_assertions::assert_eq; use tempfile::TempDir; use wiremock::matchers::header; @@ -98,7 +100,7 @@ async fn responses_stream_includes_subagent_header_on_review() { session_source, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -197,7 +199,7 @@ async fn responses_stream_includes_subagent_header_on_other() { session_source, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -354,7 +356,7 @@ async fn responses_respects_model_info_overrides_from_config() { session_source, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -393,3 +395,196 @@ async fn responses_respects_model_info_overrides_from_config() { Some("detailed") ); } + +#[tokio::test] +async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() { + core_test_support::skip_if_no_network!(); + + let server = responses::start_mock_server().await; + let response_body = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); + let provider = ModelProviderInfo { + name: "mock".into(), + base_url: Some(format!("{}/v1", server.uri())), + env_key: None, + env_key_instructions: None, + experimental_bearer_token: None, + wire_api: WireApi::Responses, + query_params: None, + http_headers: None, + env_http_headers: None, + request_max_retries: Some(0), + stream_max_retries: Some(0), + stream_idle_timeout_ms: Some(5_000), + requires_openai_auth: false, + supports_websockets: false, + }; + + let codex_home = TempDir::new().expect("failed to create TempDir"); + let mut config = load_default_config_for_test(&codex_home).await; + config.model_provider_id = provider.name.clone(); + config.model_provider = provider.clone(); + let effort = config.model_reasoning_effort; + let summary = config.model_reasoning_summary; + let model = ModelsManager::get_model_offline(config.model.as_deref()); + config.model = Some(model.clone()); + let config = Arc::new(config); + + let conversation_id = ThreadId::new(); + let auth_mode = AuthMode::Chatgpt; + let session_source = + SessionSource::SubAgent(SubAgentSource::Other("turn-metadata-e2e".to_string())); + let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); + let otel_manager = OtelManager::new( + conversation_id, + model.as_str(), + model_info.slug.as_str(), + None, + Some("test@test.com".to_string()), + Some(auth_mode), + false, + "test".to_string(), + session_source.clone(), + ); + + let client = ModelClient::new( + Arc::clone(&config), + None, + model_info, + otel_manager, + provider, + effort, + summary, + conversation_id, + session_source, + TransportManager::new(), + ); + + let workspace = TempDir::new().expect("workspace tempdir"); + let cwd = workspace.path(); + + let mut prompt = Prompt::default(); + prompt.input = vec![ResponseItem::Message { + id: None, + role: "user".into(), + content: vec![ContentItem::InputText { + text: "hello".into(), + }], + end_turn: None, + }]; + + let first_request = responses::mount_sse_once(&server, response_body.clone()).await; + let mut first_session = client.new_session(Some(cwd.to_path_buf())); + let mut first_stream = first_session + .stream(&prompt) + .await + .expect("stream first turn"); + while let Some(event) = first_stream.next().await { + if matches!(event, Ok(ResponseEvent::Completed { .. })) { + break; + } + } + assert_eq!( + first_request + .single_request() + .header("x-codex-turn-metadata"), + None + ); + + let git_config_global = cwd.join("empty-git-config"); + std::fs::write(&git_config_global, "").expect("write empty git config"); + let run_git = |args: &[&str]| { + let output = Command::new("git") + .env("GIT_CONFIG_GLOBAL", &git_config_global) + .env("GIT_CONFIG_NOSYSTEM", "1") + .args(args) + .current_dir(cwd) + .output() + .expect("git command should run"); + assert!( + output.status.success(), + "git {:?} failed: stdout={} stderr={}", + args, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + output + }; + + run_git(&["init"]); + run_git(&["config", "user.name", "Test User"]); + run_git(&["config", "user.email", "test@example.com"]); + std::fs::write(cwd.join("README.md"), "hello").expect("write README"); + run_git(&["add", "."]); + run_git(&["commit", "-m", "initial commit"]); + run_git(&[ + "remote", + "add", + "origin", + "https://github.com/openai/codex.git", + ]); + + let expected_head = String::from_utf8(run_git(&["rev-parse", "HEAD"]).stdout) + .expect("git rev-parse output should be valid UTF-8") + .trim() + .to_string(); + let expected_origin = String::from_utf8(run_git(&["remote", "get-url", "origin"]).stdout) + .expect("git remote get-url output should be valid UTF-8") + .trim() + .to_string(); + + let repo_root = std::fs::canonicalize(cwd) + .unwrap_or_else(|_| cwd.to_path_buf()) + .to_string_lossy() + .into_owned(); + let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5); + loop { + let request_recorder = responses::mount_sse_once(&server, response_body.clone()).await; + let mut session = client.new_session(Some(cwd.to_path_buf())); + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + let mut stream = session.stream(&prompt).await.expect("stream post-git turn"); + while let Some(event) = stream.next().await { + if matches!(event, Ok(ResponseEvent::Completed { .. })) { + break; + } + } + + let maybe_header = request_recorder + .single_request() + .header("x-codex-turn-metadata"); + if let Some(header_value) = maybe_header { + let parsed: serde_json::Value = serde_json::from_str(&header_value) + .expect("x-codex-turn-metadata should be valid JSON"); + let workspace = parsed + .get("workspaces") + .and_then(serde_json::Value::as_object) + .and_then(|workspaces| workspaces.get(&repo_root)) + .expect("metadata should include cwd repo root workspace entry"); + + assert_eq!( + workspace + .get("latest_git_commit_hash") + .and_then(serde_json::Value::as_str), + Some(expected_head.as_str()) + ); + assert_eq!( + workspace + .get("associated_remote_urls") + .and_then(serde_json::Value::as_object) + .and_then(|remotes| remotes.get("origin")) + .and_then(serde_json::Value::as_str), + Some(expected_origin.as_str()) + ); + return; + } + + if tokio::time::Instant::now() >= deadline { + break; + } + tokio::time::sleep(std::time::Duration::from_millis(25)).await; + } + + panic!("x-codex-turn-metadata was never observed within 5s after git setup"); +} diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 439233d8af..d79ef22b51 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1190,7 +1190,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { SessionSource::Exec, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input.push(ResponseItem::Reasoning { diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index fc0ff37cf4..1acc3756d0 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -53,7 +53,7 @@ async fn responses_websocket_streams_request() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt = prompt_with_input(vec![message_item("hello")]); stream_until_complete(&mut session, &prompt).await; @@ -83,7 +83,7 @@ async fn responses_websocket_emits_websocket_telemetry_events() { let harness = websocket_harness(&server).await; harness.otel_manager.reset_runtime_metrics(); - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt = prompt_with_input(vec![message_item("hello")]); stream_until_complete(&mut session, &prompt).await; @@ -113,7 +113,7 @@ async fn responses_websocket_emits_reasoning_included_event() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt = prompt_with_input(vec![message_item("hello")]); let mut stream = session @@ -147,7 +147,7 @@ async fn responses_websocket_appends_on_prefix() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt_one = prompt_with_input(vec![message_item("hello")]); let prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]); @@ -183,7 +183,7 @@ async fn responses_websocket_creates_on_non_prefix() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt_one = prompt_with_input(vec![message_item("hello")]); let prompt_two = prompt_with_input(vec![message_item("different")]); From d02db8b43d457852a7f2392ff58d556fbb56a9b7 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 2 Feb 2026 17:37:04 -0800 Subject: [PATCH 083/139] Add `codex app` macOS launcher (#10418) - Add `codex app ` to launch the Codex Desktop app. - On macOS, auto-downloads the DMG if missing; non-macOS prints a link to chatgpt.com/codex. --- codex-rs/cli/Cargo.toml | 2 +- codex-rs/cli/src/app_cmd.rs | 21 +++ codex-rs/cli/src/desktop_app/mac.rs | 281 ++++++++++++++++++++++++++++ codex-rs/cli/src/desktop_app/mod.rs | 11 ++ codex-rs/cli/src/main.rs | 12 ++ codex-rs/tui/tooltips.txt | 1 + 6 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 codex-rs/cli/src/app_cmd.rs create mode 100644 codex-rs/cli/src/desktop_app/mac.rs create mode 100644 codex-rs/cli/src/desktop_app/mod.rs diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index 81fcd35a91..cfcfce88f9 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -40,6 +40,7 @@ owo-colors = { workspace = true } regex-lite = { workspace = true } serde_json = { workspace = true } supports-color = { workspace = true } +tempfile = { workspace = true } tokio = { workspace = true, features = [ "io-std", "macros", @@ -59,4 +60,3 @@ assert_matches = { workspace = true } codex-utils-cargo-bin = { workspace = true } predicates = { workspace = true } pretty_assertions = { workspace = true } -tempfile = { workspace = true } diff --git a/codex-rs/cli/src/app_cmd.rs b/codex-rs/cli/src/app_cmd.rs new file mode 100644 index 0000000000..cb761c131e --- /dev/null +++ b/codex-rs/cli/src/app_cmd.rs @@ -0,0 +1,21 @@ +use clap::Parser; +use std::path::PathBuf; + +const DEFAULT_CODEX_DMG_URL: &str = "https://persistent.oaistatic.com/codex-app-prod/Codex.dmg"; + +#[derive(Debug, Parser)] +pub struct AppCommand { + /// Workspace path to open in Codex Desktop. + #[arg(value_name = "PATH", default_value = ".")] + pub path: PathBuf, + + /// Override the macOS DMG download URL (advanced). + #[arg(long, default_value = DEFAULT_CODEX_DMG_URL)] + pub download_url: String, +} + +#[cfg(target_os = "macos")] +pub async fn run_app(cmd: AppCommand) -> anyhow::Result<()> { + let workspace = std::fs::canonicalize(&cmd.path).unwrap_or(cmd.path); + crate::desktop_app::run_app_open_or_install(workspace, cmd.download_url).await +} diff --git a/codex-rs/cli/src/desktop_app/mac.rs b/codex-rs/cli/src/desktop_app/mac.rs new file mode 100644 index 0000000000..d404f5b7ca --- /dev/null +++ b/codex-rs/cli/src/desktop_app/mac.rs @@ -0,0 +1,281 @@ +use anyhow::Context as _; +use std::path::Path; +use std::path::PathBuf; +use tempfile::Builder; +use tokio::process::Command; + +pub async fn run_mac_app_open_or_install( + workspace: PathBuf, + download_url: String, +) -> anyhow::Result<()> { + if let Some(app_path) = find_existing_codex_app_path() { + eprintln!( + "Opening Codex Desktop at {app_path}...", + app_path = app_path.display() + ); + open_codex_app(&app_path, &workspace).await?; + return Ok(()); + } + eprintln!("Codex Desktop not found; downloading installer..."); + let installed_app = download_and_install_codex_to_user_applications(&download_url) + .await + .context("failed to download/install Codex Desktop")?; + eprintln!( + "Launching Codex Desktop from {installed_app}...", + installed_app = installed_app.display() + ); + open_codex_app(&installed_app, &workspace).await?; + Ok(()) +} + +fn find_existing_codex_app_path() -> Option { + candidate_codex_app_paths() + .into_iter() + .find(|candidate| candidate.is_dir()) +} + +fn candidate_codex_app_paths() -> Vec { + let mut paths = vec![PathBuf::from("/Applications/Codex.app")]; + if let Some(home) = std::env::var_os("HOME") { + paths.push(PathBuf::from(home).join("Applications").join("Codex.app")); + } + paths +} + +async fn open_codex_app(app_path: &Path, workspace: &Path) -> anyhow::Result<()> { + eprintln!( + "Opening workspace {workspace}...", + workspace = workspace.display() + ); + let status = Command::new("open") + .arg("-a") + .arg(app_path) + .arg(workspace) + .status() + .await + .context("failed to invoke `open`")?; + + if status.success() { + return Ok(()); + } + + anyhow::bail!( + "`open -a {app_path} {workspace}` exited with {status}", + app_path = app_path.display(), + workspace = workspace.display() + ); +} + +async fn download_and_install_codex_to_user_applications(dmg_url: &str) -> anyhow::Result { + let temp_dir = Builder::new() + .prefix("codex-app-installer-") + .tempdir() + .context("failed to create temp dir")?; + let tmp_root = temp_dir.path().to_path_buf(); + let _temp_dir = temp_dir; + + let dmg_path = tmp_root.join("Codex.dmg"); + download_dmg(dmg_url, &dmg_path).await?; + + eprintln!("Mounting Codex Desktop installer..."); + let mount_point = mount_dmg(&dmg_path).await?; + eprintln!( + "Installer mounted at {mount_point}.", + mount_point = mount_point.display() + ); + let result = async { + let app_in_volume = find_codex_app_in_mount(&mount_point) + .context("failed to locate Codex.app in mounted dmg")?; + install_codex_app_bundle(&app_in_volume).await + } + .await; + + let detach_result = detach_dmg(&mount_point).await; + if let Err(err) = detach_result { + eprintln!( + "warning: failed to detach dmg at {mount_point}: {err}", + mount_point = mount_point.display() + ); + } + + result +} + +async fn install_codex_app_bundle(app_in_volume: &Path) -> anyhow::Result { + for applications_dir in candidate_applications_dirs()? { + eprintln!( + "Installing Codex Desktop into {applications_dir}...", + applications_dir = applications_dir.display() + ); + std::fs::create_dir_all(&applications_dir).with_context(|| { + format!( + "failed to create applications dir {applications_dir}", + applications_dir = applications_dir.display() + ) + })?; + + let dest_app = applications_dir.join("Codex.app"); + if dest_app.is_dir() { + return Ok(dest_app); + } + + match copy_app_bundle(app_in_volume, &dest_app).await { + Ok(()) => return Ok(dest_app), + Err(err) => { + eprintln!( + "warning: failed to install Codex.app to {applications_dir}: {err}", + applications_dir = applications_dir.display() + ); + } + } + } + + anyhow::bail!("failed to install Codex.app to any applications directory"); +} + +fn candidate_applications_dirs() -> anyhow::Result> { + let mut dirs = vec![PathBuf::from("/Applications")]; + dirs.push(user_applications_dir()?); + Ok(dirs) +} + +async fn download_dmg(url: &str, dest: &Path) -> anyhow::Result<()> { + eprintln!("Downloading installer..."); + let status = Command::new("curl") + .arg("-fL") + .arg("--retry") + .arg("3") + .arg("--retry-delay") + .arg("1") + .arg("-o") + .arg(dest) + .arg(url) + .status() + .await + .context("failed to invoke `curl`")?; + + if status.success() { + return Ok(()); + } + anyhow::bail!("curl download failed with {status}"); +} + +async fn mount_dmg(dmg_path: &Path) -> anyhow::Result { + let output = Command::new("hdiutil") + .arg("attach") + .arg("-nobrowse") + .arg("-readonly") + .arg(dmg_path) + .output() + .await + .context("failed to invoke `hdiutil attach`")?; + + if !output.status.success() { + anyhow::bail!( + "`hdiutil attach` failed with {status}: {stderr}", + status = output.status, + stderr = String::from_utf8_lossy(&output.stderr) + ); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + parse_hdiutil_attach_mount_point(&stdout) + .map(PathBuf::from) + .with_context(|| format!("failed to parse mount point from hdiutil output:\n{stdout}")) +} + +async fn detach_dmg(mount_point: &Path) -> anyhow::Result<()> { + let status = Command::new("hdiutil") + .arg("detach") + .arg(mount_point) + .status() + .await + .context("failed to invoke `hdiutil detach`")?; + + if status.success() { + return Ok(()); + } + anyhow::bail!("hdiutil detach failed with {status}"); +} + +fn find_codex_app_in_mount(mount_point: &Path) -> anyhow::Result { + let direct = mount_point.join("Codex.app"); + if direct.is_dir() { + return Ok(direct); + } + + for entry in std::fs::read_dir(mount_point).with_context(|| { + format!( + "failed to read {mount_point}", + mount_point = mount_point.display() + ) + })? { + let entry = entry.context("failed to read mount directory entry")?; + let path = entry.path(); + if path.extension().is_some_and(|ext| ext == "app") && path.is_dir() { + return Ok(path); + } + } + + anyhow::bail!( + "no .app bundle found at {mount_point}", + mount_point = mount_point.display() + ); +} + +async fn copy_app_bundle(src_app: &Path, dest_app: &Path) -> anyhow::Result<()> { + let status = Command::new("ditto") + .arg(src_app) + .arg(dest_app) + .status() + .await + .context("failed to invoke `ditto`")?; + + if status.success() { + return Ok(()); + } + anyhow::bail!("ditto copy failed with {status}"); +} + +fn user_applications_dir() -> anyhow::Result { + let home = std::env::var_os("HOME").context("HOME is not set")?; + Ok(PathBuf::from(home).join("Applications")) +} + +fn parse_hdiutil_attach_mount_point(output: &str) -> Option { + output.lines().find_map(|line| { + if !line.contains("/Volumes/") { + return None; + } + if let Some((_, mount)) = line.rsplit_once('\t') { + return Some(mount.trim().to_string()); + } + line.split_whitespace() + .find(|field| field.starts_with("/Volumes/")) + .map(str::to_string) + }) +} + +#[cfg(test)] +mod tests { + use super::parse_hdiutil_attach_mount_point; + use pretty_assertions::assert_eq; + + #[test] + fn parses_mount_point_from_tab_separated_hdiutil_output() { + let output = "/dev/disk2s1\tApple_HFS\tCodex\t/Volumes/Codex\n"; + assert_eq!( + parse_hdiutil_attach_mount_point(output).as_deref(), + Some("/Volumes/Codex") + ); + } + + #[test] + fn parses_mount_point_with_spaces() { + let output = "/dev/disk2s1\tApple_HFS\tCodex Installer\t/Volumes/Codex Installer\n"; + assert_eq!( + parse_hdiutil_attach_mount_point(output).as_deref(), + Some("/Volumes/Codex Installer") + ); + } +} diff --git a/codex-rs/cli/src/desktop_app/mod.rs b/codex-rs/cli/src/desktop_app/mod.rs new file mode 100644 index 0000000000..7c42315a87 --- /dev/null +++ b/codex-rs/cli/src/desktop_app/mod.rs @@ -0,0 +1,11 @@ +#[cfg(target_os = "macos")] +mod mac; + +/// Run the app install/open logic for the current OS. +#[cfg(target_os = "macos")] +pub async fn run_app_open_or_install( + workspace: std::path::PathBuf, + download_url: String, +) -> anyhow::Result<()> { + mac::run_mac_app_open_or_install(workspace, download_url).await +} diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 01aef19176..022d6bbdf5 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -31,6 +31,10 @@ use std::io::IsTerminal; use std::path::PathBuf; use supports_color::Stream; +#[cfg(target_os = "macos")] +mod app_cmd; +#[cfg(target_os = "macos")] +mod desktop_app; mod mcp_cmd; #[cfg(not(windows))] mod wsl_paths; @@ -98,6 +102,10 @@ enum Subcommand { /// [experimental] Run the app server or related tooling. AppServer(AppServerCommand), + /// Launch the Codex desktop app (downloads the macOS installer if missing). + #[cfg(target_os = "macos")] + App(app_cmd::AppCommand), + /// Generate shell completion scripts. Completion(CompletionCommand), @@ -564,6 +572,10 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() )?; } }, + #[cfg(target_os = "macos")] + Some(Subcommand::App(app_cli)) => { + app_cmd::run_app(app_cli).await?; + } Some(Subcommand::Resume(ResumeCommand { session_id, last, diff --git a/codex-rs/tui/tooltips.txt b/codex-rs/tui/tooltips.txt index e1e4d23712..68664a6f4e 100644 --- a/codex-rs/tui/tooltips.txt +++ b/codex-rs/tui/tooltips.txt @@ -9,6 +9,7 @@ Use /status to see the current model, approvals, and token usage. Use /fork to branch the current chat into a new thread. Use /init to create an AGENTS.md with project-specific guidance. Use /mcp to list configured MCP tools. +Run `codex app` to open Codex Desktop (it installs on macOS if needed). Use /personality to customize how Codex communicates. Use /rename to rename your threads for easier thread resuming. Use the OpenAI docs MCP for API questions; enable it with `codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp`. From 1096d6453c096a1860ba8e2595729cc69e2e312b Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Mon, 2 Feb 2026 17:40:05 -0800 Subject: [PATCH 084/139] Fix plan implementation prompt reappearing after /agent thread switch (#10447) ## Summary This fixes a UX bug (https://github.com/openai/codex/issues/10442) where the **"Implement this plan?"** prompt could reappear after switching agents with `/agent` and then switching back to the original agent during plan execution. ## Root Cause On thread switch, the TUI rebuilds `ChatWidget`, replays buffered thread events, then drains any queued live events. In this flow, a `TurnComplete` can be handled twice for the same logical turn: 1. replayed (`from_replay = true`) 2. then live (`from_replay = false`) `ChatWidget` used `saw_plan_item_this_turn` to decide whether to show the plan implementation prompt, but that flag was only reset on `TurnStarted`. If duplicate completion events occurred, stale `saw_plan_item_this_turn = true` could cause the prompt to re-trigger unexpectedly. ## Fix - Clear `saw_plan_item_this_turn` at the end of `on_task_complete`, after prompt gating runs. - This keeps the flag truly turn-scoped and prevents duplicate `TurnComplete` handling from reopening the prompt. --- codex-rs/tui/src/chatwidget.rs | 5 +++ codex-rs/tui/src/chatwidget/tests.rs | 55 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index eb2d1ad965..cc99aef7cf 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1070,6 +1070,11 @@ impl ChatWidget { if !from_replay && self.queued_user_messages.is_empty() { self.maybe_prompt_plan_implementation(); } + // Keep this flag for replayed completion events so a subsequent live TurnComplete can + // still show the prompt once after thread switch replay. + if !from_replay { + self.saw_plan_item_this_turn = false; + } // If there is a queued user message, send exactly one now to begin the next turn. self.maybe_send_next_queued_input(); // Emit a notification when the turn completes (suppressed if focused). diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index a70d93a2c4..ea0cd43636 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1260,6 +1260,61 @@ async fn plan_implementation_popup_skips_replayed_turn_complete() { ); } +#[tokio::test] +async fn plan_implementation_popup_shows_once_when_replay_precedes_live_turn_complete() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; + chat.set_feature_enabled(Feature::CollaborationModes, true); + let plan_mask = + collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan) + .expect("expected plan collaboration mask"); + chat.set_collaboration_mask(plan_mask); + + chat.on_task_started(); + chat.on_plan_delta("- Step 1\n- Step 2\n".to_string()); + chat.on_plan_item_completed("- Step 1\n- Step 2\n".to_string()); + + chat.replay_initial_messages(vec![EventMsg::TurnComplete(TurnCompleteEvent { + last_agent_message: Some("Plan details".to_string()), + })]); + let replay_popup = render_bottom_popup(&chat, 80); + assert!( + !replay_popup.contains(PLAN_IMPLEMENTATION_TITLE), + "expected no prompt for replayed turn completion, got {replay_popup:?}" + ); + + chat.handle_codex_event(Event { + id: "live-turn-complete-1".to_string(), + msg: EventMsg::TurnComplete(TurnCompleteEvent { + last_agent_message: Some("Plan details".to_string()), + }), + }); + + let popup = render_bottom_popup(&chat, 80); + assert!( + popup.contains(PLAN_IMPLEMENTATION_TITLE), + "expected prompt for first live turn completion after replay, got {popup:?}" + ); + + chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)); + let dismissed_popup = render_bottom_popup(&chat, 80); + assert!( + !dismissed_popup.contains(PLAN_IMPLEMENTATION_TITLE), + "expected prompt to dismiss on Esc, got {dismissed_popup:?}" + ); + + chat.handle_codex_event(Event { + id: "live-turn-complete-2".to_string(), + msg: EventMsg::TurnComplete(TurnCompleteEvent { + last_agent_message: Some("Plan details".to_string()), + }), + }); + let duplicate_popup = render_bottom_popup(&chat, 80); + assert!( + !duplicate_popup.contains(PLAN_IMPLEMENTATION_TITLE), + "expected no prompt for duplicate live completion, got {duplicate_popup:?}" + ); +} + #[tokio::test] async fn plan_implementation_popup_skips_when_messages_queued() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; From 8f5edddf7121fdb676feac511c80da1b6d4dfb16 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Mon, 2 Feb 2026 17:41:30 -0800 Subject: [PATCH 085/139] TUI: Render request_user_input results in history and simplify interrupt handling (#10064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR improves the TUI experience for `request_user_input` by rendering submitted question/answer sets directly in conversation history with clear, structured formatting. It also intentionally simplifies interrupt behavior for now: on `Esc` / `Ctrl+C`, the questions overlay interrupts the turn without attempting to submit partial answers. Screenshot 2026-02-02 at 4 51 40 PM ## Scope - TUI-only changes. - No core/protocol/app-server behavior changes in this PR. - Resume reconstruction of interrupted question sets is out of scope for this PR. ## What Changed - Added a new history cell: `RequestUserInputResultCell` in `codex-rs/tui/src/history_cell.rs`. - On normal `request_user_input` submission, TUI now inserts that history cell immediately after sending `Op::UserInputAnswer`. - Rendering includes a `Questions` header with `answered/total` count. - Rendering shows each question as a bullet item. - Rendering styles submitted answer lines in cyan. - Rendering styles notes (for option questions) as `note:` lines in cyan. - Rendering styles freeform text (for no-option questions) as `answer:` lines in cyan. - Rendering dims only the `(unanswered)` suffix. - Rendering can include an interrupted suffix and summary text when the cell is marked interrupted. - Rendering redacts secret questions as `••••••` instead of showing raw values. - Added `wrap_with_prefix(...)` in `history_cell.rs` for wrapped prefixed lines. - Added `split_request_user_input_answer(...)` in `history_cell.rs` for decoding `"user_note: ..."` entries. ## Interrupt Behavior (Intentional for this PR) - `Esc` / `Ctrl+C` in the questions overlay now performs `Op::Interrupt` and exits the overlay. - It does **not** submit partial/committed answers on interrupt. - Added TODO comments in `request_user_input` overlay interrupt paths indicating where interrupted partial result emission should be reintroduced once core support is finalized. - Queued `request_user_input` overlays are discarded on interrupt in the current behavior. ## Tests Updated - Updated/added overlay tests in `codex-rs/tui/src/bottom_pane/request_user_input/mod.rs` to reflect interrupt-only behavior. - Added helper assertion for interrupt-only event expectation. - Existing submission-path tests now validate history insertion behavior and expected answer maps. ## Behavior Notes - Completed question flows now produce a readable `Questions` block in transcript history. - Interrupted flows currently do not persist partial answers to model-visible tool output. ## Follow-ups - Reintroduce partial-answer-on-interrupt semantics once core can persist/sequence interrupted `request_user_input` outputs safely. - Optionally add replay/resume rendering for interrupted question sets as a separate PR. ## Codex author `codex fork 019bfb8d-2a65-7313-9be2-ea7100d19a61` --- .../src/bottom_pane/request_user_input/mod.rs | 140 ++++++++++++++--- codex-rs/tui/src/history_cell.rs | 147 ++++++++++++++++++ 2 files changed, 262 insertions(+), 25 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs index 8fe3401693..59807a8a4c 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs +++ b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs @@ -27,6 +27,7 @@ use crate::bottom_pane::bottom_pane_view::BottomPaneView; use crate::bottom_pane::scroll_state::ScrollState; use crate::bottom_pane::selection_popup_common::GenericDisplayRow; use crate::bottom_pane::selection_popup_common::measure_rows_height; +use crate::history_cell; use crate::render::renderable::Renderable; use codex_core::protocol::Op; @@ -722,8 +723,17 @@ impl RequestUserInputOverlay { self.app_event_tx .send(AppEvent::CodexOp(Op::UserInputAnswer { id: self.request.turn_id.clone(), - response: RequestUserInputResponse { answers }, + response: RequestUserInputResponse { + answers: answers.clone(), + }, })); + self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new( + history_cell::RequestUserInputResultCell { + questions: self.request.questions.clone(), + answers, + interrupted: false, + }, + ))); if let Some(next) = self.queue.pop_front() { self.request = next; self.reset_for_request(); @@ -966,6 +976,8 @@ impl BottomPaneView for RequestUserInputOverlay { } if matches!(key_event.code, KeyCode::Esc) { + // TODO: Emit interrupted request_user_input results (including committed answers) + // once core supports persisting them reliably without follow-up turn issues. self.app_event_tx.send(AppEvent::CodexOp(Op::Interrupt)); self.done = true; return; @@ -1173,6 +1185,8 @@ impl BottomPaneView for RequestUserInputOverlay { fn on_ctrl_c(&mut self) -> CancellationEvent { if self.confirm_unanswered_active() { self.close_unanswered_confirmation(); + // TODO: Emit interrupted request_user_input results (including committed answers) + // once core supports persisting them reliably without follow-up turn issues. self.app_event_tx.send(AppEvent::CodexOp(Op::Interrupt)); self.done = true; return CancellationEvent::Handled; @@ -1182,6 +1196,8 @@ impl BottomPaneView for RequestUserInputOverlay { return CancellationEvent::Handled; } + // TODO: Emit interrupted request_user_input results (including committed answers) + // once core supports persisting them reliably without follow-up turn issues. self.app_event_tx.send(AppEvent::CodexOp(Op::Interrupt)); self.done = true; CancellationEvent::Handled @@ -1234,6 +1250,7 @@ mod tests { use pretty_assertions::assert_eq; use ratatui::buffer::Buffer; use ratatui::layout::Rect; + use std::collections::HashMap; use tokio::sync::mpsc::unbounded_channel; use unicode_width::UnicodeWidthStr; @@ -1245,6 +1262,18 @@ mod tests { (AppEventSender::new(tx_raw), rx) } + fn expect_interrupt_only(rx: &mut tokio::sync::mpsc::UnboundedReceiver) { + let event = rx.try_recv().expect("expected interrupt AppEvent"); + let AppEvent::CodexOp(op) = event else { + panic!("expected CodexOp"); + }; + assert_eq!(op, Op::Interrupt); + assert!( + rx.try_recv().is_err(), + "unexpected AppEvents before interrupt completion" + ); + } + fn question_with_options(id: &str, header: &str) -> RequestUserInputQuestion { RequestUserInputQuestion { id: id.to_string(), @@ -1389,6 +1418,33 @@ mod tests { assert_eq!(overlay.request.turn_id, "turn-3"); } + #[test] + fn interrupt_discards_queued_requests_and_emits_interrupt() { + let (tx, mut rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event("turn-1", vec![question_with_options("q1", "First")]), + tx, + true, + false, + false, + ); + overlay.try_consume_user_input_request(RequestUserInputEvent { + call_id: "call-2".to_string(), + turn_id: "turn-2".to_string(), + questions: vec![question_with_options("q2", "Second")], + }); + overlay.try_consume_user_input_request(RequestUserInputEvent { + call_id: "call-3".to_string(), + turn_id: "turn-3".to_string(), + questions: vec![question_with_options("q3", "Third")], + }); + + overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); + + assert!(overlay.done, "expected overlay to be done"); + expect_interrupt_only(&mut rx); + } + #[test] fn options_can_submit_empty_when_unanswered() { let (tx, mut rx) = test_sender(); @@ -1403,7 +1459,7 @@ mod tests { overlay.submit_answers(); let event = rx.try_recv().expect("expected AppEvent"); - let AppEvent::CodexOp(Op::UserInputAnswer { id, response }) = event else { + let AppEvent::CodexOp(Op::UserInputAnswer { id, response, .. }) = event else { panic!("expected UserInputAnswer"); }; assert_eq!(id, "turn-1"); @@ -1454,15 +1510,30 @@ mod tests { let first_answer = &overlay.answers[0]; assert!(first_answer.answer_committed); assert_eq!(first_answer.options_state.selected_idx, Some(0)); - assert!(rx.try_recv().is_err()); + assert!( + rx.try_recv().is_err(), + "unexpected AppEvent before full submission" + ); overlay.handle_key_event(KeyEvent::from(KeyCode::Enter)); let event = rx.try_recv().expect("expected AppEvent"); let AppEvent::CodexOp(Op::UserInputAnswer { response, .. }) = event else { panic!("expected UserInputAnswer"); }; - let answer = response.answers.get("q1").expect("answer missing"); - assert_eq!(answer.answers, vec!["Option 1".to_string()]); + let mut expected = HashMap::new(); + expected.insert( + "q1".to_string(), + RequestUserInputAnswer { + answers: vec!["Option 1".to_string()], + }, + ); + expected.insert( + "q2".to_string(), + RequestUserInputAnswer { + answers: vec!["Option 1".to_string()], + }, + ); + assert_eq!(response.answers, expected); } #[test] @@ -1630,6 +1701,10 @@ mod tests { overlay.handle_key_event(KeyEvent::from(KeyCode::Enter)); assert!(overlay.confirm_unanswered_active()); + assert!( + rx.try_recv().is_err(), + "unexpected AppEvent before confirmation submit" + ); overlay.handle_key_event(KeyEvent::from(KeyCode::Char('1'))); overlay.handle_key_event(KeyEvent::from(KeyCode::Enter)); @@ -1657,11 +1732,7 @@ mod tests { overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); assert_eq!(overlay.done, true); - let event = rx.try_recv().expect("expected AppEvent"); - let AppEvent::CodexOp(op) = event else { - panic!("expected CodexOp"); - }; - assert_eq!(op, Op::Interrupt); + expect_interrupt_only(&mut rx); } #[test] @@ -1678,11 +1749,7 @@ mod tests { overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); assert_eq!(overlay.done, true); - let event = rx.try_recv().expect("expected AppEvent"); - let AppEvent::CodexOp(op) = event else { - panic!("expected CodexOp"); - }; - assert_eq!(op, Op::Interrupt); + expect_interrupt_only(&mut rx); } #[test] @@ -1697,16 +1764,13 @@ mod tests { ); let answer = overlay.current_answer_mut().expect("answer missing"); answer.options_state.selected_idx = Some(0); + answer.answer_committed = true; overlay.handle_key_event(KeyEvent::from(KeyCode::Tab)); overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); assert_eq!(overlay.done, true); - let event = rx.try_recv().expect("expected AppEvent"); - let AppEvent::CodexOp(op) = event else { - panic!("expected CodexOp"); - }; - assert_eq!(op, Op::Interrupt); + expect_interrupt_only(&mut rx); } #[test] @@ -1721,17 +1785,42 @@ mod tests { ); let answer = overlay.current_answer_mut().expect("answer missing"); answer.options_state.selected_idx = Some(0); + answer.answer_committed = true; overlay.handle_key_event(KeyEvent::from(KeyCode::Tab)); overlay.handle_key_event(KeyEvent::from(KeyCode::Char('a'))); overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); assert_eq!(overlay.done, true); - let event = rx.try_recv().expect("expected AppEvent"); - let AppEvent::CodexOp(op) = event else { - panic!("expected CodexOp"); - }; - assert_eq!(op, Op::Interrupt); + expect_interrupt_only(&mut rx); + } + + #[test] + fn esc_drops_committed_answers() { + let (tx, mut rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event( + "turn-1", + vec![ + question_with_options("q1", "First"), + question_without_options("q2", "Second"), + ], + ), + tx, + true, + false, + false, + ); + + overlay.handle_key_event(KeyEvent::from(KeyCode::Enter)); + assert!( + rx.try_recv().is_err(), + "unexpected AppEvent before interruption" + ); + + overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); + + expect_interrupt_only(&mut rx); } #[test] @@ -1961,6 +2050,7 @@ mod tests { overlay.composer.move_cursor_to_end(); overlay.handle_key_event(KeyEvent::from(KeyCode::Enter)); assert_eq!(overlay.answers[0].answer_committed, true); + let _ = rx.try_recv(); overlay.move_question(false); overlay diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index eb1de452a9..0258e05968 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -52,6 +52,8 @@ use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; use codex_protocol::plan_tool::UpdatePlanArgs; +use codex_protocol::request_user_input::RequestUserInputAnswer; +use codex_protocol::request_user_input::RequestUserInputQuestion; use codex_protocol::user_input::TextElement; use image::DynamicImage; use image::ImageReader; @@ -1762,6 +1764,151 @@ pub(crate) fn new_error_event(message: String) -> PlainHistoryCell { PlainHistoryCell { lines } } +/// Renders a completed (or interrupted) request_user_input exchange in history. +#[derive(Debug)] +pub(crate) struct RequestUserInputResultCell { + pub(crate) questions: Vec, + pub(crate) answers: HashMap, + pub(crate) interrupted: bool, +} + +impl HistoryCell for RequestUserInputResultCell { + fn display_lines(&self, width: u16) -> Vec> { + let width = width.max(1) as usize; + let total = self.questions.len(); + let answered = self + .questions + .iter() + .filter(|question| { + self.answers + .get(&question.id) + .is_some_and(|answer| !answer.answers.is_empty()) + }) + .count(); + let unanswered = total.saturating_sub(answered); + + let mut header = vec!["•".dim(), " ".into(), "Questions".bold()]; + header.push(format!(" {answered}/{total} answered").dim()); + if self.interrupted { + header.push(" (interrupted)".cyan()); + } + + let mut lines: Vec> = vec![header.into()]; + + for question in &self.questions { + let answer = self.answers.get(&question.id); + let answer_missing = match answer { + Some(answer) => answer.answers.is_empty(), + None => true, + }; + let mut question_lines = wrap_with_prefix( + &question.question, + width, + " • ".into(), + " ".into(), + Style::default(), + ); + if answer_missing && let Some(last) = question_lines.last_mut() { + last.spans.push(" (unanswered)".dim()); + } + lines.extend(question_lines); + + let Some(answer) = answer.filter(|answer| !answer.answers.is_empty()) else { + continue; + }; + if question.is_secret { + lines.extend(wrap_with_prefix( + "••••••", + width, + " answer: ".dim(), + " ".dim(), + Style::default().fg(Color::Cyan), + )); + continue; + } + + let (options, note) = split_request_user_input_answer(answer); + + for option in options { + lines.extend(wrap_with_prefix( + &option, + width, + " answer: ".dim(), + " ".dim(), + Style::default().fg(Color::Cyan), + )); + } + if let Some(note) = note { + let (label, continuation, style) = if question.options.is_some() { + ( + " note: ".dim(), + " ".dim(), + Style::default().fg(Color::Cyan), + ) + } else { + ( + " answer: ".dim(), + " ".dim(), + Style::default().fg(Color::Cyan), + ) + }; + lines.extend(wrap_with_prefix(¬e, width, label, continuation, style)); + } + } + + if self.interrupted && unanswered > 0 { + let summary = format!("interrupted with {unanswered} unanswered"); + lines.extend(wrap_with_prefix( + &summary, + width, + " ↳ ".cyan().dim(), + " ".dim(), + Style::default().fg(Color::Cyan).add_modifier(Modifier::DIM), + )); + } + + lines + } +} + +/// Wrap a plain string with textwrap and prefix each line, while applying a style to the content. +fn wrap_with_prefix( + text: &str, + width: usize, + initial_prefix: Span<'static>, + subsequent_prefix: Span<'static>, + style: Style, +) -> Vec> { + let prefix_width = initial_prefix + .content + .width() + .max(subsequent_prefix.content.width()); + let wrap_width = width.saturating_sub(prefix_width).max(1); + let wrapped = textwrap::wrap(text, wrap_width); + let wrapped_lines = wrapped + .into_iter() + .map(|segment| Span::from(segment.to_string()).set_style(style).into()) + .collect::>>(); + prefix_lines(wrapped_lines, initial_prefix, subsequent_prefix) +} + +/// Split a request_user_input answer into option labels and an optional freeform note. +/// Notes are encoded as "user_note: " entries in the answers list. +fn split_request_user_input_answer( + answer: &RequestUserInputAnswer, +) -> (Vec, Option) { + let mut options = Vec::new(); + let mut note = None; + for entry in &answer.answers { + if let Some(note_text) = entry.strip_prefix("user_note: ") { + note = Some(note_text.to_string()); + } else { + options.push(entry.clone()); + } + } + (options, note) +} + /// Render a user‑friendly plan update styled like a checkbox todo list. pub(crate) fn new_plan_update(update: UpdatePlanArgs) -> PlanUpdateCell { let UpdatePlanArgs { explanation, plan } = update; From 66447d5d2cbe390fbe6468f2c4395197fce72935 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Mon, 2 Feb 2026 17:41:55 -0800 Subject: [PATCH 086/139] feat: replace custom mcp-types crate with equivalents from rmcp (#10349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356 --- codex-rs/Cargo.lock | 14 +- codex-rs/Cargo.toml | 1 - codex-rs/app-server-protocol/Cargo.toml | 1 - .../schema/json/EventMsg.json | 420 +-------- .../schema/json/ServerNotification.json | 424 +-------- .../codex_app_server_protocol.schemas.json | 836 +----------------- .../json/v1/ForkConversationResponse.json | 420 +-------- .../json/v1/ResumeConversationResponse.json | 420 +-------- .../v1/SessionConfiguredNotification.json | 420 +-------- .../json/v2/ItemCompletedNotification.json | 278 +----- .../json/v2/ItemStartedNotification.json | 278 +----- .../json/v2/ListMcpServerStatusResponse.json | 174 +--- .../schema/json/v2/ReviewStartResponse.json | 278 +----- .../schema/json/v2/ThreadForkResponse.json | 278 +----- .../schema/json/v2/ThreadListResponse.json | 278 +----- .../schema/json/v2/ThreadReadResponse.json | 278 +----- .../schema/json/v2/ThreadResumeResponse.json | 278 +----- .../json/v2/ThreadRollbackResponse.json | 278 +----- .../schema/json/v2/ThreadStartResponse.json | 278 +----- .../json/v2/ThreadStartedNotification.json | 278 +----- .../json/v2/ThreadUnarchiveResponse.json | 278 +----- .../json/v2/TurnCompletedNotification.json | 278 +----- .../schema/json/v2/TurnStartResponse.json | 278 +----- .../json/v2/TurnStartedNotification.json | 278 +----- .../schema/typescript/Annotations.ts | 9 - .../schema/typescript/AudioContent.ts | 9 - .../schema/typescript/BlobResourceContents.ts | 5 - .../schema/typescript/CallToolResult.ts | 3 +- .../schema/typescript/ContentBlock.ts | 10 - .../typescript/ElicitationRequestEvent.ts | 3 +- .../schema/typescript/EmbeddedResource.ts | 13 - .../typescript/EmbeddedResourceResource.ts | 7 - .../schema/typescript/ImageContent.ts | 9 - .../schema/typescript/Resource.ts | 4 +- .../schema/typescript/ResourceLink.ts | 11 - .../schema/typescript/ResourceTemplate.ts | 4 +- .../schema/typescript/Role.ts | 8 - .../schema/typescript/TextContent.ts | 9 - .../schema/typescript/TextResourceContents.ts | 5 - .../schema/typescript/Tool.ts | 6 +- .../schema/typescript/ToolAnnotations.ts | 15 - .../schema/typescript/ToolInputSchema.ts | 9 - .../schema/typescript/ToolOutputSchema.ts | 10 - .../schema/typescript/index.ts | 14 - .../schema/typescript/v2/McpToolCallResult.ts | 3 +- .../app-server-protocol/src/protocol/v2.rs | 14 +- .../src/schema_fixtures.rs | 90 +- codex-rs/app-server-test-client/Cargo.lock | 11 - codex-rs/app-server/Cargo.toml | 2 - .../app-server/src/bespoke_event_handling.rs | 15 +- codex-rs/core/Cargo.toml | 7 +- codex-rs/core/src/codex.rs | 55 +- codex-rs/core/src/mcp/mod.rs | 122 ++- codex-rs/core/src/mcp_connection_manager.rs | 111 ++- codex-rs/core/src/mcp_tool_call.rs | 9 +- codex-rs/core/src/tools/context.rs | 2 +- .../core/src/tools/handlers/mcp_resource.rs | 66 +- codex-rs/core/src/tools/router.rs | 3 +- codex-rs/core/src/tools/spec.rs | 295 +++--- codex-rs/core/tests/common/lib.rs | 2 +- codex-rs/core/tests/suite/rmcp_client.rs | 13 +- codex-rs/exec/Cargo.toml | 3 +- .../src/event_processor_with_human_output.rs | 3 +- codex-rs/exec/src/exec_events.rs | 10 +- .../tests/event_processor_with_json_output.rs | 19 +- codex-rs/mcp-server/Cargo.toml | 2 +- codex-rs/mcp-server/src/codex_tool_config.rs | 81 +- codex-rs/mcp-server/src/codex_tool_runner.rs | 33 +- codex-rs/mcp-server/src/error_code.rs | 2 - codex-rs/mcp-server/src/exec_approval.rs | 35 +- codex-rs/mcp-server/src/lib.rs | 27 +- codex-rs/mcp-server/src/message_processor.rs | 517 +++++------ codex-rs/mcp-server/src/outgoing_message.rs | 125 ++- codex-rs/mcp-server/src/patch_approval.rs | 30 +- codex-rs/mcp-server/tests/common/Cargo.toml | 2 +- codex-rs/mcp-server/tests/common/lib.rs | 6 +- .../mcp-server/tests/common/mcp_process.rs | 168 ++-- codex-rs/mcp-server/tests/suite/codex_tool.rs | 158 ++-- codex-rs/protocol/Cargo.toml | 1 - codex-rs/protocol/src/approvals.rs | 3 +- codex-rs/protocol/src/mcp.rs | 16 +- codex-rs/protocol/src/models.rs | 149 +++- codex-rs/protocol/src/protocol.rs | 10 +- codex-rs/rmcp-client/Cargo.toml | 1 - .../rmcp-client/src/logging_client_handler.rs | 7 +- codex-rs/rmcp-client/src/rmcp_client.rs | 101 +-- codex-rs/rmcp-client/src/utils.rs | 84 -- codex-rs/rmcp-client/tests/resources.rs | 79 +- codex-rs/tui/Cargo.toml | 2 +- .../tui/src/bottom_pane/approval_overlay.rs | 2 +- ...et__tests__binary_size_ideal_response.snap | 2 +- codex-rs/tui/src/history_cell.rs | 219 +++-- 92 files changed, 1629 insertions(+), 8273 deletions(-) delete mode 100644 codex-rs/app-server-protocol/schema/typescript/Annotations.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AudioContent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ImageContent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/Role.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TextContent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts delete mode 100644 codex-rs/mcp-server/src/error_code.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 716bc4cd5a..bc8cac244c 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1108,7 +1108,6 @@ dependencies = [ "codex-utils-absolute-path", "codex-utils-json-to-toml", "core_test_support", - "mcp-types", "os_info", "pretty_assertions", "rmcp", @@ -1137,7 +1136,6 @@ dependencies = [ "codex-utils-absolute-path", "codex-utils-cargo-bin", "inventory", - "mcp-types", "pretty_assertions", "schemars 0.8.22", "serde", @@ -1437,7 +1435,6 @@ dependencies = [ "landlock", "libc", "maplit", - "mcp-types", "multimap", "once_cell", "openssl-sys", @@ -1449,6 +1446,7 @@ dependencies = [ "regex", "regex-lite", "reqwest", + "rmcp", "schemars 0.8.22", "seccompiler", "serde", @@ -1512,10 +1510,10 @@ dependencies = [ "codex-utils-cargo-bin", "core_test_support", "libc", - "mcp-types", "owo-colors", "predicates", "pretty_assertions", + "rmcp", "serde", "serde_json", "shlex", @@ -1717,10 +1715,10 @@ dependencies = [ "codex-protocol", "codex-utils-json-to-toml", "core_test_support", - "mcp-types", "mcp_test_support", "os_info", "pretty_assertions", + "rmcp", "schemars 0.8.22", "serde", "serde_json", @@ -1829,7 +1827,6 @@ dependencies = [ "icu_decimal", "icu_locale_core", "icu_provider", - "mcp-types", "mime_guess", "pretty_assertions", "schemars 0.8.22", @@ -1873,7 +1870,6 @@ dependencies = [ "codex-utils-home-dir", "futures", "keyring", - "mcp-types", "oauth2", "pretty_assertions", "reqwest", @@ -1966,7 +1962,6 @@ dependencies = [ "itertools 0.14.0", "lazy_static", "libc", - "mcp-types", "pathdiff", "pretty_assertions", "pulldown-cmark", @@ -1975,6 +1970,7 @@ dependencies = [ "ratatui-macros", "regex-lite", "reqwest", + "rmcp", "serde", "serde_json", "serial_test", @@ -4672,9 +4668,9 @@ dependencies = [ "codex-mcp-server", "codex-utils-cargo-bin", "core_test_support", - "mcp-types", "os_info", "pretty_assertions", + "rmcp", "serde", "serde_json", "shlex", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 3171c8a96c..a452e98e7c 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -112,7 +112,6 @@ codex-utils-string = { path = "utils/string" } codex-windows-sandbox = { path = "windows-sandbox-rs" } core_test_support = { path = "core/tests/common" } exec_server_test_support = { path = "exec-server/tests/common" } -mcp-types = { path = "mcp-types" } mcp_test_support = { path = "mcp-server/tests/common" } # External diff --git a/codex-rs/app-server-protocol/Cargo.toml b/codex-rs/app-server-protocol/Cargo.toml index 2788801051..7b2b8c53d7 100644 --- a/codex-rs/app-server-protocol/Cargo.toml +++ b/codex-rs/app-server-protocol/Cargo.toml @@ -17,7 +17,6 @@ clap = { workspace = true, features = ["derive"] } codex-protocol = { workspace = true } codex-experimental-api-macros = { workspace = true } codex-utils-absolute-path = { workspace = true } -mcp-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index 407626d82d..9e3cbb4156 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index b6777d5c8b..6df9a010b1 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -165,34 +165,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -226,36 +198,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "AuthMode": { "description": "Authentication mode for OpenAI-backed providers.", "oneOf": [ @@ -298,27 +240,6 @@ }, "type": "object" }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -362,10 +283,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -889,25 +809,6 @@ ], "type": "object" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -1099,42 +1000,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "ErrorNotification": { "properties": { "error": { @@ -3714,36 +3579,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "ItemCompletedNotification": { "properties": { "item": { @@ -4037,9 +3872,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -4641,7 +4474,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -4697,22 +4531,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -4745,74 +4578,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -5403,14 +5172,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -5874,32 +5635,6 @@ ], "type": "object" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -5982,27 +5717,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { @@ -6714,38 +6428,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -6759,82 +6461,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "Turn": { "properties": { "error": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index aca840f6cb..e92d46e7b6 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -123,34 +123,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "ApplyPatchApprovalParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -258,36 +230,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "AuthMode": { "description": "Authentication mode for OpenAI-backed providers.", "oneOf": [ @@ -332,27 +274,6 @@ "title": "AuthStatusChangeNotification", "type": "object" }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -377,10 +298,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -2105,25 +2025,6 @@ "title": "CommandExecutionRequestApprovalResponse", "type": "object" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -2383,42 +2284,6 @@ "title": "DynamicToolCallResponse", "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", @@ -5408,36 +5273,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "InitializeCapabilities": { "description": "Client-declared capabilities negotiated during initialize.", "properties": { @@ -6539,7 +6374,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -6595,22 +6431,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -6643,74 +6478,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -7431,14 +7202,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxMode": { "enum": [ "read-only", @@ -8841,32 +8604,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -8890,27 +8627,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -8971,38 +8687,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -9016,82 +8720,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "ToolRequestUserInputAnswer": { "description": "EXPERIMENTAL. Captures a user's answer to a request_user_input question.", "properties": { @@ -9940,34 +9568,6 @@ }, "type": "object" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/v2/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AppInfo": { "properties": { "description": { @@ -10072,36 +9672,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "AuthMode": { "description": "Authentication mode for OpenAI-backed providers.", "oneOf": [ @@ -10128,27 +9698,6 @@ } ] }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -11280,25 +10829,6 @@ "title": "ConfigWriteResponse", "type": "object" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/v2/TextContent" - }, - { - "$ref": "#/definitions/v2/ImageContent" - }, - { - "$ref": "#/definitions/v2/AudioContent" - }, - { - "$ref": "#/definitions/v2/ResourceLink" - }, - { - "$ref": "#/definitions/v2/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -11440,42 +10970,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/v2/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/v2/TextResourceContents" - }, - { - "$ref": "#/definitions/v2/BlobResourceContents" - } - ] - }, "ErrorNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -11769,36 +11263,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "ItemCompletedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -12242,9 +11706,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/v2/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -12882,22 +12344,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -12930,74 +12391,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -13520,14 +12917,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxMode": { "enum": [ "read-only", @@ -14050,32 +13439,6 @@ "title": "TerminalInteractionNotification", "type": "object" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -14135,27 +13498,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { @@ -15499,38 +14841,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/v2/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/v2/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -15544,82 +14874,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "ToolsV2": { "properties": { "view_image": { diff --git a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json index f4c68a9f9d..ae6e705221 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json index fcb39cd05c..cdad2018c6 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json index 24e71bc27a..e6ed4d0427 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index 866ce9aa2b..56ef813231 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -263,61 +184,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -337,36 +203,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -381,9 +217,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -468,95 +302,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -580,27 +325,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index 2fd71404f6..625c99af93 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -263,61 +184,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -337,36 +203,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -381,9 +217,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -468,95 +302,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -580,27 +325,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json index 2ee4926965..fc181c2702 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json @@ -1,34 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "McpAuthStatus": { "enum": [ "unsupported", @@ -77,22 +49,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -128,16 +99,7 @@ "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -169,49 +131,29 @@ ], "type": "object" }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -224,82 +166,6 @@ "name" ], "type": "object" - }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" } }, "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index 341ff78c0a..dfbd76a20b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 0a3826d8fd..61b12ceff3 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -5,34 +5,6 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "enum": [ "untrusted", @@ -42,57 +14,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -418,61 +339,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -515,36 +381,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -559,9 +395,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -665,69 +499,6 @@ ], "type": "string" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "oneOf": [ { @@ -900,32 +671,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -949,27 +694,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index eb4ec81f68..50c3d60e37 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index 02dab752e3..c4fa3b2028 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index dad4ccbfeb..0534f6e16e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -5,34 +5,6 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "enum": [ "untrusted", @@ -42,57 +14,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -418,61 +339,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -515,36 +381,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -559,9 +395,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -665,69 +499,6 @@ ], "type": "string" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "oneOf": [ { @@ -900,32 +671,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -949,27 +694,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 3ebb5052d3..148bbe7d7d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index d88d3694de..c85d7ce97a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -5,34 +5,6 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "enum": [ "untrusted", @@ -42,57 +14,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -418,61 +339,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -515,36 +381,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -559,9 +395,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -665,69 +499,6 @@ ], "type": "string" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "oneOf": [ { @@ -900,32 +671,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -949,27 +694,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 0c8d47136e..44aca775c1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 46478554bb..055c3fd4a9 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 918f82a456..798caa5995 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index d0d09a3f20..65b2a66be0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 9dd23a33e6..1a63c0d7d0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/typescript/Annotations.ts b/codex-rs/app-server-protocol/schema/typescript/Annotations.ts deleted file mode 100644 index b89cbd725f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/Annotations.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Role } from "./Role"; - -/** - * Optional annotations for the client. The client can use annotations to inform how objects are used or displayed - */ -export type Annotations = { audience?: Array, lastModified?: string, priority?: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts b/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts deleted file mode 100644 index 90dd930c93..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * Audio provided to or from an LLM. - */ -export type AudioContent = { annotations?: Annotations, data: string, mimeType: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts b/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts deleted file mode 100644 index 362adcfe14..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type BlobResourceContents = { blob: string, mimeType?: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts index 6c6b590d1a..e7a471d465 100644 --- a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts +++ b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts @@ -1,10 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ContentBlock } from "./ContentBlock"; import type { JsonValue } from "./serde_json/JsonValue"; /** * The server's response to a tool call. */ -export type CallToolResult = { content: Array, isError?: boolean, structuredContent?: JsonValue, }; +export type CallToolResult = { content: Array, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts b/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts deleted file mode 100644 index 2dd62d87b4..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AudioContent } from "./AudioContent"; -import type { EmbeddedResource } from "./EmbeddedResource"; -import type { ImageContent } from "./ImageContent"; -import type { ResourceLink } from "./ResourceLink"; -import type { TextContent } from "./TextContent"; - -export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; diff --git a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts index a4d641fd44..045e304bdc 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts @@ -1,6 +1,5 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RequestId } from "./RequestId"; -export type ElicitationRequestEvent = { server_name: string, id: RequestId, message: string, }; +export type ElicitationRequestEvent = { server_name: string, id: string | number, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts deleted file mode 100644 index 1b81e37ab1..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts +++ /dev/null @@ -1,13 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; -import type { EmbeddedResourceResource } from "./EmbeddedResourceResource"; - -/** - * The contents of a resource, embedded into a prompt or tool call result. - * - * It is up to the client how best to render embedded resources for the benefit - * of the LLM and/or the user. - */ -export type EmbeddedResource = { annotations?: Annotations, resource: EmbeddedResourceResource, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts deleted file mode 100644 index 58f70f6e82..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { BlobResourceContents } from "./BlobResourceContents"; -import type { TextResourceContents } from "./TextResourceContents"; - -export type EmbeddedResourceResource = TextResourceContents | BlobResourceContents; diff --git a/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts b/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts deleted file mode 100644 index 0537639c02..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * An image provided to or from an LLM. - */ -export type ImageContent = { annotations?: Annotations, data: string, mimeType: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Resource.ts b/codex-rs/app-server-protocol/schema/typescript/Resource.ts index 238cd9008c..6eca7941d6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/Resource.ts +++ b/codex-rs/app-server-protocol/schema/typescript/Resource.ts @@ -1,9 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; +import type { JsonValue } from "./serde_json/JsonValue"; /** * A known resource that the server is capable of reading. */ -export type Resource = { annotations?: Annotations, description?: string, mimeType?: string, name: string, size?: bigint, title?: string, uri: string, }; +export type Resource = { annotations?: JsonValue, description?: string, mimeType?: string, name: string, size?: number, title?: string, uri: string, icons?: Array, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts b/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts deleted file mode 100644 index fe934c3a5d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts +++ /dev/null @@ -1,11 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * A resource that the server is capable of reading, included in a prompt or tool call result. - * - * Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. - */ -export type ResourceLink = { annotations?: Annotations, description?: string, mimeType?: string, name: string, size?: bigint, title?: string, type: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts b/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts index 03c9a116c8..6dc395129c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts @@ -1,9 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; +import type { JsonValue } from "./serde_json/JsonValue"; /** * A template description for resources available on the server. */ -export type ResourceTemplate = { annotations?: Annotations, description?: string, mimeType?: string, name: string, title?: string, uriTemplate: string, }; +export type ResourceTemplate = { annotations?: JsonValue, uriTemplate: string, name: string, title?: string, description?: string, mimeType?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Role.ts b/codex-rs/app-server-protocol/schema/typescript/Role.ts deleted file mode 100644 index 009307efce..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/Role.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * The sender or recipient of messages and data in a conversation. - */ -export type Role = "assistant" | "user"; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextContent.ts b/codex-rs/app-server-protocol/schema/typescript/TextContent.ts deleted file mode 100644 index 760102f81d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TextContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * Text provided to or from an LLM. - */ -export type TextContent = { annotations?: Annotations, text: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts b/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts deleted file mode 100644 index 5bef79210c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type TextResourceContents = { mimeType?: string, text: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Tool.ts b/codex-rs/app-server-protocol/schema/typescript/Tool.ts index 8255eecf2c..b795916140 100644 --- a/codex-rs/app-server-protocol/schema/typescript/Tool.ts +++ b/codex-rs/app-server-protocol/schema/typescript/Tool.ts @@ -1,11 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ToolAnnotations } from "./ToolAnnotations"; -import type { ToolInputSchema } from "./ToolInputSchema"; -import type { ToolOutputSchema } from "./ToolOutputSchema"; +import type { JsonValue } from "./serde_json/JsonValue"; /** * Definition for a tool the client can call. */ -export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; +export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts b/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts deleted file mode 100644 index 70348a9964..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Additional properties describing a Tool to clients. - * - * NOTE: all properties in ToolAnnotations are **hints**. - * They are not guaranteed to provide a faithful description of - * tool behavior (including descriptive properties like `title`). - * - * Clients should never make tool use decisions based on ToolAnnotations - * received from untrusted servers. - */ -export type ToolAnnotations = { destructiveHint?: boolean, idempotentHint?: boolean, openWorldHint?: boolean, readOnlyHint?: boolean, title?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts b/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts deleted file mode 100644 index 78f40bbe40..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "./serde_json/JsonValue"; - -/** - * A JSON Schema object defining the expected parameters for the tool. - */ -export type ToolInputSchema = { properties?: JsonValue, required?: Array, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts b/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts deleted file mode 100644 index 9270c8e7b7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "./serde_json/JsonValue"; - -/** - * An optional JSON Schema object defining the structure of the tool's output returned in - * the structuredContent field of a CallToolResult. - */ -export type ToolOutputSchema = { properties?: JsonValue, required?: Array, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index fafc6aad9d..191f993864 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -14,18 +14,15 @@ export type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawCont export type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; export type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; export type { AgentStatus } from "./AgentStatus"; -export type { Annotations } from "./Annotations"; export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams"; export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse"; export type { ArchiveConversationParams } from "./ArchiveConversationParams"; export type { ArchiveConversationResponse } from "./ArchiveConversationResponse"; export type { AskForApproval } from "./AskForApproval"; -export type { AudioContent } from "./AudioContent"; export type { AuthMode } from "./AuthMode"; export type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification"; export type { BackgroundEventEvent } from "./BackgroundEventEvent"; -export type { BlobResourceContents } from "./BlobResourceContents"; export type { ByteRange } from "./ByteRange"; export type { CallToolResult } from "./CallToolResult"; export type { CancelLoginChatGptParams } from "./CancelLoginChatGptParams"; @@ -44,7 +41,6 @@ export type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; export type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; export type { CollaborationMode } from "./CollaborationMode"; export type { CollaborationModeMask } from "./CollaborationModeMask"; -export type { ContentBlock } from "./ContentBlock"; export type { ContentItem } from "./ContentItem"; export type { ContextCompactedEvent } from "./ContextCompactedEvent"; export type { ContextCompactionItem } from "./ContextCompactionItem"; @@ -55,8 +51,6 @@ export type { CustomPrompt } from "./CustomPrompt"; export type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; export type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; export type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; -export type { EmbeddedResource } from "./EmbeddedResource"; -export type { EmbeddedResourceResource } from "./EmbeddedResourceResource"; export type { ErrorEvent } from "./ErrorEvent"; export type { EventMsg } from "./EventMsg"; export type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; @@ -92,7 +86,6 @@ export type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams"; export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse"; export type { GitSha } from "./GitSha"; export type { HistoryEntry } from "./HistoryEntry"; -export type { ImageContent } from "./ImageContent"; export type { InitializeCapabilities } from "./InitializeCapabilities"; export type { InitializeParams } from "./InitializeParams"; export type { InitializeResponse } from "./InitializeResponse"; @@ -152,7 +145,6 @@ export type { RequestUserInputEvent } from "./RequestUserInputEvent"; export type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; export type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; export type { Resource } from "./Resource"; -export type { ResourceLink } from "./ResourceLink"; export type { ResourceTemplate } from "./ResourceTemplate"; export type { ResponseItem } from "./ResponseItem"; export type { ResumeConversationParams } from "./ResumeConversationParams"; @@ -164,7 +156,6 @@ export type { ReviewLineRange } from "./ReviewLineRange"; export type { ReviewOutputEvent } from "./ReviewOutputEvent"; export type { ReviewRequest } from "./ReviewRequest"; export type { ReviewTarget } from "./ReviewTarget"; -export type { Role } from "./Role"; export type { SandboxMode } from "./SandboxMode"; export type { SandboxPolicy } from "./SandboxPolicy"; export type { SandboxSettings } from "./SandboxSettings"; @@ -191,9 +182,7 @@ export type { StepStatus } from "./StepStatus"; export type { StreamErrorEvent } from "./StreamErrorEvent"; export type { SubAgentSource } from "./SubAgentSource"; export type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; -export type { TextContent } from "./TextContent"; export type { TextElement } from "./TextElement"; -export type { TextResourceContents } from "./TextResourceContents"; export type { ThreadId } from "./ThreadId"; export type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; export type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; @@ -201,9 +190,6 @@ export type { TokenCountEvent } from "./TokenCountEvent"; export type { TokenUsage } from "./TokenUsage"; export type { TokenUsageInfo } from "./TokenUsageInfo"; export type { Tool } from "./Tool"; -export type { ToolAnnotations } from "./ToolAnnotations"; -export type { ToolInputSchema } from "./ToolInputSchema"; -export type { ToolOutputSchema } from "./ToolOutputSchema"; export type { Tools } from "./Tools"; export type { TurnAbortReason } from "./TurnAbortReason"; export type { TurnAbortedEvent } from "./TurnAbortedEvent"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts index 93f942a7f9..f493a86094 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts @@ -1,7 +1,6 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ContentBlock } from "../ContentBlock"; import type { JsonValue } from "../serde_json/JsonValue"; -export type McpToolCallResult = { content: Array, structuredContent: JsonValue | null, }; +export type McpToolCallResult = { content: Array, structuredContent: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 3ca344ee49..4f37e2889d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -15,6 +15,9 @@ use codex_protocol::config_types::Verbosity; use codex_protocol::config_types::WebSearchMode; use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent; use codex_protocol::items::TurnItem as CoreTurnItem; +use codex_protocol::mcp::Resource as McpResource; +use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate; +use codex_protocol::mcp::Tool as McpTool; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand; @@ -41,10 +44,6 @@ use codex_protocol::user_input::ByteRange as CoreByteRange; use codex_protocol::user_input::TextElement as CoreTextElement; use codex_protocol::user_input::UserInput as CoreUserInput; use codex_utils_absolute_path::AbsolutePathBuf; -use mcp_types::ContentBlock as McpContentBlock; -use mcp_types::Resource as McpResource; -use mcp_types::ResourceTemplate as McpResourceTemplate; -use mcp_types::Tool as McpTool; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -2360,7 +2359,12 @@ impl From for CollabAgentState { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpToolCallResult { - pub content: Vec, + // NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a more precise Rust + // representation of MCP content blocks. We intentionally use `serde_json::Value` here because + // this crate exports JSON schema + TS types (`schemars`/`ts-rs`), and the rmcp model types + // aren't set up to be schema/TS friendly (and would introduce heavier coupling to rmcp's Rust + // representations). Using `JsonValue` keeps the payload wire-shaped and easy to export. + pub content: Vec, pub structured_content: Option, } diff --git a/codex-rs/app-server-protocol/src/schema_fixtures.rs b/codex-rs/app-server-protocol/src/schema_fixtures.rs index 918011e95c..5412da8632 100644 --- a/codex-rs/app-server-protocol/src/schema_fixtures.rs +++ b/codex-rs/app-server-protocol/src/schema_fixtures.rs @@ -2,6 +2,7 @@ use anyhow::Context; use anyhow::Result; use serde_json::Map; use serde_json::Value; +use std::cmp::Ordering; use std::collections::BTreeMap; use std::path::Path; use std::path::PathBuf; @@ -92,7 +93,49 @@ fn read_file_bytes(path: &Path) -> Result> { fn canonicalize_json(value: &Value) -> Value { match value { - Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()), + Value::Array(items) => { + // NOTE: We sort some JSON arrays to make schema fixture comparisons stable across + // platforms. + // + // In general, JSON array ordering is significant. However, this code path is used + // only by `schema_fixtures_match_generated` to compare our *vendored* JSON schema + // files against freshly generated output. Some parts of schema generation end up + // with non-deterministic ordering across platforms (often due to map iteration order + // upstream), which can cause Windows CI failures even when the generated schema is + // semantically equivalent. + // + // JSON Schema itself also contains a number of array-valued keywords whose ordering + // does not affect validation semantics (e.g. `required`, `type`, `enum`, `anyOf`, + // `oneOf`, `allOf`). That makes it reasonable to treat many schema-emitted arrays as + // order-insensitive for the purpose of fixture diffs. + // + // To avoid accidentally changing the meaning of arrays where order *could* matter + // (e.g. tuple validation / `prefixItems`-style arrays), we only sort arrays when we + // can derive a stable sort key for *every* element. If we cannot, we preserve the + // original ordering. + let items = items.iter().map(canonicalize_json).collect::>(); + let mut sortable = Vec::with_capacity(items.len()); + for item in &items { + let Some(key) = schema_array_item_sort_key(item) else { + return Value::Array(items); + }; + let stable = serde_json::to_string(item).unwrap_or_default(); + sortable.push((key, stable)); + } + + let mut items = items.into_iter().zip(sortable).collect::>(); + + items.sort_by( + |(_, (key_left, stable_left)), (_, (key_right, stable_right))| match key_left + .cmp(key_right) + { + Ordering::Equal => stable_left.cmp(stable_right), + other => other, + }, + ); + + Value::Array(items.into_iter().map(|(item, _)| item).collect()) + } Value::Object(map) => { let mut entries: Vec<_> = map.iter().collect(); entries.sort_by(|(left, _), (right, _)| left.cmp(right)); @@ -106,6 +149,25 @@ fn canonicalize_json(value: &Value) -> Value { } } +fn schema_array_item_sort_key(item: &Value) -> Option { + match item { + Value::Null => Some("null".to_string()), + Value::Bool(b) => Some(format!("b:{b}")), + Value::Number(n) => Some(format!("n:{n}")), + Value::String(s) => Some(format!("s:{s}")), + Value::Object(map) => { + if let Some(Value::String(reference)) = map.get("$ref") { + Some(format!("ref:{reference}")) + } else if let Some(Value::String(title)) = map.get("title") { + Some(format!("title:{title}")) + } else { + None + } + } + Value::Array(_) => None, + } +} + fn collect_files_recursive(root: &Path) -> Result>> { let mut files = BTreeMap::new(); @@ -146,3 +208,29 @@ fn collect_files_recursive(root: &Path) -> Result>> { Ok(files) } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn canonicalize_json_sorts_string_arrays() { + let value = serde_json::json!(["b", "a"]); + let expected = serde_json::json!(["a", "b"]); + assert_eq!(canonicalize_json(&value), expected); + } + + #[test] + fn canonicalize_json_sorts_schema_ref_arrays() { + let value = serde_json::json!([ + {"$ref": "#/definitions/B"}, + {"$ref": "#/definitions/A"} + ]); + let expected = serde_json::json!([ + {"$ref": "#/definitions/A"}, + {"$ref": "#/definitions/B"} + ]); + assert_eq!(canonicalize_json(&value), expected); + } +} diff --git a/codex-rs/app-server-test-client/Cargo.lock b/codex-rs/app-server-test-client/Cargo.lock index 1720850cd2..c6e4241d2c 100644 --- a/codex-rs/app-server-test-client/Cargo.lock +++ b/codex-rs/app-server-test-client/Cargo.lock @@ -175,7 +175,6 @@ dependencies = [ "base64", "icu_decimal", "icu_locale_core", - "mcp-types", "mime_guess", "serde", "serde_json", @@ -521,16 +520,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "mcp-types" -version = "0.45.0" -source = "git+https://github.com/openai/codex.git?tag=rust-v0.45.0#a7c7869c23f88f6c468281e6f438ba4a91b81f26" -dependencies = [ - "serde", - "serde_json", - "ts-rs", -] - [[package]] name = "memchr" version = "2.7.6" diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 11c57ebbc8..778d57bab0 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -35,7 +35,6 @@ codex-utils-json-to-toml = { workspace = true } chrono = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -mcp-types = { workspace = true } tempfile = { workspace = true } time = { workspace = true } toml = { workspace = true } @@ -60,7 +59,6 @@ axum = { workspace = true, default-features = false, features = [ base64 = { workspace = true } codex-execpolicy = { workspace = true } core_test_support = { workspace = true } -mcp-types = { workspace = true } os_info = { workspace = true } pretty_assertions = { workspace = true } rmcp = { workspace = true, default-features = false, features = [ diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index bf8fad3a9c..55b185e5a8 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -1841,12 +1841,11 @@ mod tests { use codex_core::protocol::RateLimitWindow; use codex_core::protocol::TokenUsage; use codex_core::protocol::TokenUsageInfo; + use codex_protocol::mcp::CallToolResult; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; - use mcp_types::CallToolResult; - use mcp_types::ContentBlock; - use mcp_types::TextContent; use pretty_assertions::assert_eq; + use rmcp::model::Content; use serde_json::Value as JsonValue; use std::collections::HashMap; use std::time::Duration; @@ -2374,15 +2373,15 @@ mod tests { #[tokio::test] async fn test_construct_mcp_tool_call_end_notification_success() { - let content = vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: "{\"resources\":[]}".to_string(), - r#type: "text".to_string(), - })]; + let content = vec![ + serde_json::to_value(Content::text("{\"resources\":[]}")) + .expect("content should serialize"), + ]; let result = CallToolResult { content: content.clone(), is_error: Some(false), structured_content: None, + meta: None, }; let end_event = McpToolCallEndEvent { diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index a95981cca2..38da38b929 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -57,7 +57,6 @@ indexmap = { workspace = true } indoc = { workspace = true } keyring = { workspace = true, features = ["crypto-rust"] } libc = { workspace = true } -mcp-types = { workspace = true } multimap = { workspace = true } once_cell = { workspace = true } os_info = { workspace = true } @@ -65,6 +64,12 @@ rand = { workspace = true } regex = { workspace = true } regex-lite = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } +rmcp = { workspace = true, default-features = false, features = [ + "base64", + "macros", + "schemars", + "server", +] } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 31a81846e2..6cc389b531 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -50,6 +50,7 @@ use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::items::PlanItem; use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::BaseInstructions; use codex_protocol::models::format_allow_prefixes; use codex_protocol::openai_models::ModelInfo; @@ -72,14 +73,12 @@ use codex_rmcp_client::OAuthCredentialsStoreMode; use futures::future::BoxFuture; use futures::prelude::*; use futures::stream::FuturesOrdered; -use mcp_types::CallToolResult; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::RequestId; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::PaginatedRequestParam; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::RequestId; use serde_json; use serde_json::Value; use tokio::sync::Mutex; @@ -2224,7 +2223,7 @@ impl Session { pub async fn list_resources( &self, server: &str, - params: Option, + params: Option, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2237,7 +2236,7 @@ impl Session { pub async fn list_resource_templates( &self, server: &str, - params: Option, + params: Option, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2250,7 +2249,7 @@ impl Session { pub async fn read_resource( &self, server: &str, - params: ReadResourceRequestParams, + params: ReadResourceRequestParam, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2577,10 +2576,10 @@ mod handlers { use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; use codex_protocol::dynamic_tools::DynamicToolResponse; + use codex_protocol::mcp::RequestId as ProtocolRequestId; use codex_protocol::user_input::UserInput; use codex_rmcp_client::ElicitationAction; use codex_rmcp_client::ElicitationResponse; - use mcp_types::RequestId; use std::path::PathBuf; use std::sync::Arc; use tracing::info; @@ -2709,7 +2708,7 @@ mod handlers { pub async fn resolve_elicitation( sess: &Arc, server_name: String, - request_id: RequestId, + request_id: ProtocolRequestId, decision: codex_protocol::approvals::ElicitationAction, ) { let action = match decision { @@ -2724,6 +2723,12 @@ mod handlers { ElicitationAction::Decline | ElicitationAction::Cancel => None, }; let response = ElicitationResponse { action, content }; + let request_id = match request_id { + ProtocolRequestId::String(value) => { + rmcp::model::NumberOrString::String(std::sync::Arc::from(value)) + } + ProtocolRequestId::Integer(value) => rmcp::model::NumberOrString::Number(value), + }; if let Err(err) = sess .resolve_elicitation(server_name, request_id, response) .await @@ -4516,8 +4521,7 @@ mod tests { use std::time::Duration; use tokio::time::sleep; - use mcp_types::ContentBlock; - use mcp_types::TextContent; + use codex_protocol::mcp::CallToolResult as McpCallToolResult; use pretty_assertions::assert_eq; use serde::Deserialize; use serde_json::json; @@ -5101,7 +5105,7 @@ mod tests { #[test] fn prefers_structured_content_when_present() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { // Content present but should be ignored because structured_content is set. content: vec![text_block("ignored")], is_error: None, @@ -5109,6 +5113,7 @@ mod tests { "ok": true, "value": 42 })), + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5147,10 +5152,11 @@ mod tests { #[test] fn falls_back_to_content_when_structured_is_null() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { content: vec![text_block("hello"), text_block("world")], is_error: None, structured_content: Some(serde_json::Value::Null), + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5166,10 +5172,11 @@ mod tests { #[test] fn success_flag_reflects_is_error_true() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { content: vec![text_block("unused")], is_error: Some(true), structured_content: Some(json!({ "message": "bad" })), + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5184,10 +5191,11 @@ mod tests { #[test] fn success_flag_true_with_no_error_and_content_used() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { content: vec![text_block("alpha")], is_error: Some(false), structured_content: None, + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5238,11 +5246,10 @@ mod tests { } } - fn text_block(s: &str) -> ContentBlock { - ContentBlock::TextContent(TextContent { - annotations: None, - text: s.to_string(), - r#type: "text".to_string(), + fn text_block(s: &str) -> serde_json::Value { + json!({ + "type": "text", + "text": s, }) } diff --git a/codex-rs/core/src/mcp/mod.rs b/codex-rs/core/src/mcp/mod.rs index 12c61ddaf1..cb0b5e2f26 100644 --- a/codex-rs/core/src/mcp/mod.rs +++ b/codex-rs/core/src/mcp/mod.rs @@ -1,6 +1,5 @@ pub mod auth; mod skill_dependencies; - pub(crate) use skill_dependencies::maybe_prompt_and_install_mcp_dependencies; use std::collections::HashMap; @@ -9,9 +8,12 @@ use std::path::PathBuf; use std::time::Duration; use async_channel::unbounded; +use codex_protocol::mcp::Resource; +use codex_protocol::mcp::ResourceTemplate; +use codex_protocol::mcp::Tool; use codex_protocol::protocol::McpListToolsResponseEvent; use codex_protocol::protocol::SandboxPolicy; -use mcp_types::Tool as McpTool; +use serde_json::Value; use tokio_util::sync::CancellationToken; use crate::AuthManager; @@ -201,8 +203,8 @@ pub fn split_qualified_tool_name(qualified_name: &str) -> Option<(String, String } pub fn group_tools_by_server( - tools: &HashMap, -) -> HashMap> { + tools: &HashMap, +) -> HashMap> { let mut grouped = HashMap::new(); for (qualified_name, tool) in tools { if let Some((server_name, tool_name)) = split_qualified_tool_name(qualified_name) { @@ -230,11 +232,96 @@ pub(crate) async fn collect_mcp_snapshot_from_manager( .map(|(name, entry)| (name.clone(), entry.auth_status)) .collect(); + let tools = tools + .into_iter() + .filter_map(|(name, tool)| match serde_json::to_value(tool.tool) { + Ok(value) => match Tool::from_mcp_value(value) { + Ok(tool) => Some((name, tool)), + Err(err) => { + tracing::warn!("Failed to convert MCP tool '{name}': {err}"); + None + } + }, + Err(err) => { + tracing::warn!("Failed to serialize MCP tool '{name}': {err}"); + None + } + }) + .collect(); + + let resources = resources + .into_iter() + .map(|(name, resources)| { + let resources = resources + .into_iter() + .filter_map(|resource| match serde_json::to_value(resource) { + Ok(value) => match Resource::from_mcp_value(value.clone()) { + Ok(resource) => Some(resource), + Err(err) => { + let (uri, resource_name) = match value { + Value::Object(obj) => ( + obj.get("uri") + .and_then(|v| v.as_str().map(ToString::to_string)), + obj.get("name") + .and_then(|v| v.as_str().map(ToString::to_string)), + ), + _ => (None, None), + }; + + tracing::warn!( + "Failed to convert MCP resource (uri={uri:?}, name={resource_name:?}): {err}" + ); + None + } + }, + Err(err) => { + tracing::warn!("Failed to serialize MCP resource: {err}"); + None + } + }) + .collect::>(); + (name, resources) + }) + .collect(); + + let resource_templates = resource_templates + .into_iter() + .map(|(name, templates)| { + let templates = templates + .into_iter() + .filter_map(|template| match serde_json::to_value(template) { + Ok(value) => match ResourceTemplate::from_mcp_value(value.clone()) { + Ok(template) => Some(template), + Err(err) => { + let (uri_template, template_name) = match value { + Value::Object(obj) => ( + obj.get("uriTemplate") + .or_else(|| obj.get("uri_template")) + .and_then(|v| v.as_str().map(ToString::to_string)), + obj.get("name") + .and_then(|v| v.as_str().map(ToString::to_string)), + ), + _ => (None, None), + }; + + tracing::warn!( + "Failed to convert MCP resource template (uri_template={uri_template:?}, name={template_name:?}): {err}" + ); + None + } + }, + Err(err) => { + tracing::warn!("Failed to serialize MCP resource template: {err}"); + None + } + }) + .collect::>(); + (name, templates) + }) + .collect(); + McpListToolsResponseEvent { - tools: tools - .into_iter() - .map(|(name, tool)| (name, tool.tool)) - .collect(), + tools, resources, resource_templates, auth_statuses, @@ -244,21 +331,18 @@ pub(crate) async fn collect_mcp_snapshot_from_manager( #[cfg(test)] mod tests { use super::*; - use mcp_types::ToolInputSchema; use pretty_assertions::assert_eq; - fn make_tool(name: &str) -> McpTool { - McpTool { - annotations: None, - description: None, - input_schema: ToolInputSchema { - properties: None, - required: None, - r#type: "object".to_string(), - }, + fn make_tool(name: &str) -> Tool { + Tool { name: name.to_string(), - output_schema: None, title: None, + description: None, + input_schema: serde_json::json!({"type": "object", "properties": {}}), + output_schema: None, + annotations: None, + icons: None, + meta: None, } } diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 1b5048ab0c..1a5ec559b8 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -23,6 +23,8 @@ use async_channel::Sender; use codex_async_utils::CancelErr; use codex_async_utils::OrCancelExt; use codex_protocol::approvals::ElicitationRequestEvent; +use codex_protocol::mcp::CallToolResult; +use codex_protocol::mcp::RequestId as ProtocolRequestId; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::McpStartupCompleteEvent; @@ -37,22 +39,23 @@ use codex_rmcp_client::SendElicitation; use futures::future::BoxFuture; use futures::future::FutureExt; use futures::future::Shared; -use mcp_types::ClientCapabilities; -use mcp_types::Implementation; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::RequestId; -use mcp_types::Resource; -use mcp_types::ResourceTemplate; -use mcp_types::Tool; +use rmcp::model::ClientCapabilities; +use rmcp::model::ElicitationCapability; +use rmcp::model::Implementation; +use rmcp::model::InitializeRequestParam; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::PaginatedRequestParam; +use rmcp::model::ProtocolVersion; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::RequestId; +use rmcp::model::Resource; +use rmcp::model::ResourceTemplate; +use rmcp::model::Tool; use serde::Deserialize; use serde::Serialize; -use serde_json::json; use sha1::Digest; use sha1::Sha1; use tokio::sync::Mutex; @@ -198,7 +201,14 @@ impl ElicitationRequestManager { id: "mcp_elicitation_request".to_string(), msg: EventMsg::ElicitationRequest(ElicitationRequestEvent { server_name, - id, + id: match id.clone() { + rmcp::model::NumberOrString::String(value) => { + ProtocolRequestId::String(value.to_string()) + } + rmcp::model::NumberOrString::Number(value) => { + ProtocolRequestId::Integer(value) + } + }, message: elicitation.message, }), }) @@ -493,7 +503,7 @@ impl McpConnectionManager { let mut cursor: Option = None; loop { - let params = cursor.as_ref().map(|next| ListResourcesRequestParams { + let params = cursor.as_ref().map(|next| PaginatedRequestParam { cursor: Some(next.clone()), }); let response = match client.list_resources(params, timeout).await { @@ -558,11 +568,9 @@ impl McpConnectionManager { let mut cursor: Option = None; loop { - let params = cursor - .as_ref() - .map(|next| ListResourceTemplatesRequestParams { - cursor: Some(next.clone()), - }); + let params = cursor.as_ref().map(|next| PaginatedRequestParam { + cursor: Some(next.clone()), + }); let response = match client.list_resource_templates(params, timeout).await { Ok(result) => result, Err(err) => return (server_name_cloned, Err(err)), @@ -615,7 +623,7 @@ impl McpConnectionManager { server: &str, tool: &str, arguments: Option, - ) -> Result { + ) -> Result { let client = self.client_by_name(server).await?; if !client.tool_filter.allows(tool) { return Err(anyhow!( @@ -623,18 +631,34 @@ impl McpConnectionManager { )); } - client + let result: rmcp::model::CallToolResult = client .client .call_tool(tool.to_string(), arguments, client.tool_timeout) .await - .with_context(|| format!("tool call failed for `{server}/{tool}`")) + .with_context(|| format!("tool call failed for `{server}/{tool}`"))?; + + let content = result + .content + .into_iter() + .map(|content| { + serde_json::to_value(content) + .unwrap_or_else(|_| serde_json::Value::String("".to_string())) + }) + .collect(); + + Ok(CallToolResult { + content, + structured_content: result.structured_content, + is_error: result.is_error, + meta: result.meta.and_then(|meta| serde_json::to_value(meta).ok()), + }) } /// List resources from the specified server. pub async fn list_resources( &self, server: &str, - params: Option, + params: Option, ) -> Result { let managed = self.client_by_name(server).await?; let timeout = managed.tool_timeout; @@ -650,7 +674,7 @@ impl McpConnectionManager { pub async fn list_resource_templates( &self, server: &str, - params: Option, + params: Option, ) -> Result { let managed = self.client_by_name(server).await?; let client = managed.client.clone(); @@ -666,7 +690,7 @@ impl McpConnectionManager { pub async fn read_resource( &self, server: &str, - params: ReadResourceRequestParams, + params: ReadResourceRequestParam, ) -> Result { let managed = self.client_by_name(server).await?; let client = managed.client.clone(); @@ -849,25 +873,25 @@ async fn start_server_task( tx_event: Sender, elicitation_requests: ElicitationRequestManager, ) -> Result { - let params = mcp_types::InitializeRequestParams { + let params = InitializeRequestParam { capabilities: ClientCapabilities { experimental: None, roots: None, sampling: None, // https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation#capabilities // indicates this should be an empty object. - elicitation: Some(json!({})), + elicitation: Some(ElicitationCapability { + schema_validation: None, + }), }, client_info: Implementation { name: "codex-mcp-client".to_owned(), version: env!("CARGO_PKG_VERSION").to_owned(), title: Some("Codex".into()), - // This field is used by Codex when it is an MCP - // server: it should not be used when Codex is - // an MCP client. - user_agent: None, + icons: None, + website_url: None, }, - protocol_version: mcp_types::MCP_SCHEMA_VERSION.to_owned(), + protocol_version: ProtocolVersion::V_2025_06_18, }; let send_elicitation = elicitation_requests.make_sender(server_name.clone(), tx_event); @@ -964,7 +988,7 @@ async fn list_tools_for_client( } ToolInfo { server_name: server_name.to_owned(), - tool_name: tool_def.name.clone(), + tool_name: tool_def.name.to_string(), tool: tool_def, connector_id: tool.connector_id, connector_name, @@ -1047,24 +1071,23 @@ mod mcp_init_error_display_tests {} mod tests { use super::*; use codex_protocol::protocol::McpAuthStatus; - use mcp_types::ToolInputSchema; + use rmcp::model::JsonObject; use std::collections::HashSet; + use std::sync::Arc; fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo { ToolInfo { server_name: server_name.to_string(), tool_name: tool_name.to_string(), tool: Tool { - annotations: None, - description: Some(format!("Test tool: {tool_name}")), - input_schema: ToolInputSchema { - properties: None, - required: None, - r#type: "object".to_string(), - }, - name: tool_name.to_string(), - output_schema: None, + name: tool_name.to_string().into(), title: None, + description: Some(format!("Test tool: {tool_name}").into()), + input_schema: Arc::new(JsonObject::default()), + output_schema: None, + annotations: None, + icons: None, + meta: None, }, connector_id: None, connector_name: None, diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 6425392fb5..75248f34cc 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -10,6 +10,7 @@ use crate::protocol::EventMsg; use crate::protocol::McpInvocation; use crate::protocol::McpToolCallBeginEvent; use crate::protocol::McpToolCallEndEvent; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::protocol::AskForApproval; @@ -18,7 +19,7 @@ use codex_protocol::request_user_input::RequestUserInputArgs; use codex_protocol::request_user_input::RequestUserInputQuestion; use codex_protocol::request_user_input::RequestUserInputQuestionOption; use codex_protocol::request_user_input::RequestUserInputResponse; -use mcp_types::ToolAnnotations; +use rmcp::model::ToolAnnotations; use std::sync::Arc; /// Handles the specified tool call dispatches the appropriate @@ -72,7 +73,7 @@ pub(crate) async fn handle_mcp_tool_call( .await; let start = Instant::now(); - let result = sess + let result: Result = sess .call_tool(&server, &tool_name, arguments_value.clone()) .await .map_err(|e| format!("tool call error: {e:?}")); @@ -134,7 +135,7 @@ pub(crate) async fn handle_mcp_tool_call( let start = Instant::now(); // Perform the tool call. - let result = sess + let result: Result = sess .call_tool(&server, &tool_name, arguments_value.clone()) .await .map_err(|e| format!("tool call error: {e:?}")); @@ -341,7 +342,7 @@ async fn notify_mcp_tool_call_skip( call_id: &str, invocation: McpInvocation, message: String, -) -> Result { +) -> Result { let tool_call_begin_event = EventMsg::McpToolCallBegin(McpToolCallBeginEvent { call_id: call_id.to_string(), invocation: invocation.clone(), diff --git a/codex-rs/core/src/tools/context.rs b/codex-rs/core/src/tools/context.rs index abe488681e..f0bbb158f5 100644 --- a/codex-rs/core/src/tools/context.rs +++ b/codex-rs/core/src/tools/context.rs @@ -4,12 +4,12 @@ use crate::tools::TELEMETRY_PREVIEW_MAX_BYTES; use crate::tools::TELEMETRY_PREVIEW_MAX_LINES; use crate::tools::TELEMETRY_PREVIEW_TRUNCATION_NOTICE; use crate::turn_diff_tracker::TurnDiffTracker; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ShellToolCallParams; use codex_utils_string::take_bytes_at_char_boundary; -use mcp_types::CallToolResult; use std::borrow::Cow; use std::sync::Arc; use tokio::sync::Mutex; diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index 62f7a83e1a..df421bd6d3 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -4,17 +4,14 @@ use std::time::Duration; use std::time::Instant; use async_trait::async_trait; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::Resource; -use mcp_types::ResourceTemplate; -use mcp_types::TextContent; +use codex_protocol::mcp::CallToolResult; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::PaginatedRequestParam; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::Resource; +use rmcp::model::ResourceTemplate; use serde::Deserialize; use serde::Serialize; use serde::de::DeserializeOwned; @@ -264,7 +261,7 @@ async fn handle_list_resources( let payload_result: Result = async { if let Some(server_name) = server.clone() { - let params = cursor.clone().map(|value| ListResourcesRequestParams { + let params = cursor.clone().map(|value| PaginatedRequestParam { cursor: Some(value), }); let result = session @@ -371,11 +368,9 @@ async fn handle_list_resource_templates( let payload_result: Result = async { if let Some(server_name) = server.clone() { - let params = cursor - .clone() - .map(|value| ListResourceTemplatesRequestParams { - cursor: Some(value), - }); + let params = cursor.clone().map(|value| PaginatedRequestParam { + cursor: Some(value), + }); let result = session .list_resource_templates(&server_name, params) .await @@ -482,7 +477,7 @@ async fn handle_read_resource( let payload_result: Result = async { let result = session - .read_resource(&server, ReadResourceRequestParams { uri: uri.clone() }) + .read_resource(&server, ReadResourceRequestParam { uri: uri.clone() }) .await .map_err(|err| { FunctionCallError::RespondToModel(format!("resources/read failed: {err:#}")) @@ -551,13 +546,10 @@ async fn handle_read_resource( fn call_tool_result_from_content(content: &str, success: Option) -> CallToolResult { CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: content.to_string(), - r#type: "text".to_string(), - })], - is_error: success.map(|value| !value), + content: vec![serde_json::json!({"type": "text", "text": content})], structured_content: None, + is_error: success.map(|value| !value), + meta: None, } } @@ -678,32 +670,33 @@ where #[cfg(test)] mod tests { use super::*; - use mcp_types::ListResourcesResult; - use mcp_types::ResourceTemplate; use pretty_assertions::assert_eq; + use rmcp::model::AnnotateAble; use serde_json::json; fn resource(uri: &str, name: &str) -> Resource { - Resource { - annotations: None, + rmcp::model::RawResource { + uri: uri.to_string(), + name: name.to_string(), + title: None, description: None, mime_type: None, - name: name.to_string(), size: None, - title: None, - uri: uri.to_string(), + icons: None, + meta: None, } + .no_annotation() } fn template(uri_template: &str, name: &str) -> ResourceTemplate { - ResourceTemplate { - annotations: None, - description: None, - mime_type: None, + rmcp::model::RawResourceTemplate { + uri_template: uri_template.to_string(), name: name.to_string(), title: None, - uri_template: uri_template.to_string(), + description: None, + mime_type: None, } + .no_annotation() } #[test] @@ -719,6 +712,7 @@ mod tests { #[test] fn list_resources_payload_from_single_server_copies_next_cursor() { let result = ListResourcesResult { + meta: None, next_cursor: Some("cursor-1".to_string()), resources: vec![resource("memo://id", "memo")], }; diff --git a/codex-rs/core/src/tools/router.rs b/codex-rs/core/src/tools/router.rs index d3ff84a2b0..51328ccc9f 100644 --- a/codex-rs/core/src/tools/router.rs +++ b/codex-rs/core/src/tools/router.rs @@ -15,6 +15,7 @@ use codex_protocol::models::LocalShellAction; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use codex_protocol::models::ShellToolCallParams; +use rmcp::model::Tool; use std::collections::HashMap; use std::sync::Arc; use tracing::instrument; @@ -34,7 +35,7 @@ pub struct ToolRouter { impl ToolRouter { pub fn from_config( config: &ToolsConfig, - mcp_tools: Option>, + mcp_tools: Option>, dynamic_tools: &[DynamicToolSpec], ) -> Self { let builder = build_specs(config, mcp_tools, dynamic_tools); diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index a51372b9b3..c08feeef9a 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -1087,20 +1087,26 @@ pub(crate) fn create_tools_json_for_chat_completions_api( pub(crate) fn mcp_tool_to_openai_tool( fully_qualified_name: String, - tool: mcp_types::Tool, + tool: rmcp::model::Tool, ) -> Result { - let mcp_types::Tool { + let rmcp::model::Tool { description, - mut input_schema, + input_schema, .. } = tool; - // OpenAI models mandate the "properties" field in the schema. The Agents - // SDK fixed this by inserting an empty object for "properties" if it is not - // already present https://github.com/openai/openai-agents-python/issues/449 - // so here we do the same. - if input_schema.properties.is_none() { - input_schema.properties = Some(serde_json::Value::Object(serde_json::Map::new())); + let mut serialized_input_schema = serde_json::Value::Object(input_schema.as_ref().clone()); + + // OpenAI models mandate the "properties" field in the schema. Some MCP + // servers omit it (or set it to null), so we insert an empty object to + // match the behavior of the Agents SDK. + if let serde_json::Value::Object(obj) = &mut serialized_input_schema + && obj.get("properties").is_none_or(serde_json::Value::is_null) + { + obj.insert( + "properties".to_string(), + serde_json::Value::Object(serde_json::Map::new()), + ); } // Serialize to a raw JSON value so we can sanitize schemas coming from MCP @@ -1108,13 +1114,12 @@ pub(crate) fn mcp_tool_to_openai_tool( // Schemas (e.g. using enum/anyOf), or use unsupported variants like // `integer`. Our internal JsonSchema is a small subset and requires // `type`, so we coerce/sanitize here for compatibility. - let mut serialized_input_schema = serde_json::to_value(input_schema)?; sanitize_json_schema(&mut serialized_input_schema); let input_schema = serde_json::from_value::(serialized_input_schema)?; Ok(ResponsesApiTool { name: fully_qualified_name, - description: description.unwrap_or_default(), + description: description.map(Into::into).unwrap_or_default(), strict: false, parameters: input_schema, }) @@ -1254,7 +1259,7 @@ fn sanitize_json_schema(value: &mut JsonValue) { /// Builds the tool registry builder while collecting tool specs for later serialization. pub(crate) fn build_specs( config: &ToolsConfig, - mcp_tools: Option>, + mcp_tools: Option>, dynamic_tools: &[DynamicToolSpec], ) -> ToolRegistryBuilder { use crate::tools::handlers::ApplyPatchHandler; @@ -1410,7 +1415,7 @@ pub(crate) fn build_specs( } if let Some(mcp_tools) = mcp_tools { - let mut entries: Vec<(String, mcp_types::Tool)> = mcp_tools.into_iter().collect(); + let mut entries: Vec<(String, rmcp::model::Tool)> = mcp_tools.into_iter().collect(); entries.sort_by(|a, b| a.0.cmp(&b.0)); for (name, tool) in entries.into_iter() { @@ -1452,11 +1457,50 @@ mod tests { use crate::config::test_config; use crate::models_manager::manager::ModelsManager; use crate::tools::registry::ConfiguredToolSpec; - use mcp_types::ToolInputSchema; use pretty_assertions::assert_eq; use super::*; + fn mcp_tool( + name: &str, + description: &str, + input_schema: serde_json::Value, + ) -> rmcp::model::Tool { + rmcp::model::Tool { + name: name.to_string().into(), + title: None, + description: Some(description.to_string().into()), + input_schema: std::sync::Arc::new(rmcp::model::object(input_schema)), + output_schema: None, + annotations: None, + icons: None, + meta: None, + } + } + + #[test] + fn mcp_tool_to_openai_tool_inserts_empty_properties() { + let mut schema = rmcp::model::JsonObject::new(); + schema.insert("type".to_string(), serde_json::json!("object")); + + let tool = rmcp::model::Tool { + name: "no_props".to_string().into(), + title: None, + description: Some("No properties".to_string().into()), + input_schema: std::sync::Arc::new(schema), + output_schema: None, + annotations: None, + icons: None, + meta: None, + }; + + let openai_tool = + mcp_tool_to_openai_tool("server/no_props".to_string(), tool).expect("convert tool"); + let parameters = serde_json::to_value(openai_tool.parameters).expect("serialize schema"); + + assert_eq!(parameters.get("properties"), Some(&serde_json::json!({}))); + } + fn tool_name(tool: &ToolSpec) -> &str { match tool { ToolSpec::Function(ResponsesApiTool { name, .. }) => name, @@ -2026,37 +2070,26 @@ mod tests { &tools_config, Some(HashMap::from([( "test_server/do_something_cool".to_string(), - mcp_types::Tool { - name: "do_something_cool".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "string_argument": { - "type": "string", - }, - "number_argument": { - "type": "number", - }, + mcp_tool( + "do_something_cool", + "Do something cool", + serde_json::json!({ + "type": "object", + "properties": { + "string_argument": { "type": "string" }, + "number_argument": { "type": "number" }, "object_argument": { "type": "object", "properties": { "string_property": { "type": "string" }, "number_property": { "type": "number" }, }, - "required": [ - "string_property", - "number_property", - ], - "additionalProperties": Some(false), + "required": ["string_property", "number_property"], + "additionalProperties": false, }, - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Do something cool".to_string()), - }, + }, + }), + ), )])), &[], ) @@ -2120,51 +2153,18 @@ mod tests { }); // Intentionally construct a map with keys that would sort alphabetically. - let tools_map: HashMap = HashMap::from([ + let tools_map: HashMap = HashMap::from([ ( "test_server/do".to_string(), - mcp_types::Tool { - name: "a".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({})), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("a".to_string()), - }, + mcp_tool("a", "a", serde_json::json!({"type": "object"})), ), ( "test_server/something".to_string(), - mcp_types::Tool { - name: "b".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({})), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("b".to_string()), - }, + mcp_tool("b", "b", serde_json::json!({"type": "object"})), ), ( "test_server/cool".to_string(), - mcp_types::Tool { - name: "c".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({})), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("c".to_string()), - }, + mcp_tool("c", "c", serde_json::json!({"type": "object"})), ), ]); @@ -2200,22 +2200,16 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/search".to_string(), - mcp_types::Tool { - name: "search".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "query": { - "description": "search query" - } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Search docs".to_string()), - }, + mcp_tool( + "search", + "Search docs", + serde_json::json!({ + "type": "object", + "properties": { + "query": {"description": "search query"} + } + }), + ), )])), &[], ) @@ -2258,20 +2252,14 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/paginate".to_string(), - mcp_types::Tool { - name: "paginate".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "page": { "type": "integer" } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Pagination".to_string()), - }, + mcp_tool( + "paginate", + "Pagination", + serde_json::json!({ + "type": "object", + "properties": {"page": {"type": "integer"}} + }), + ), )])), &[], ) @@ -2313,20 +2301,14 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/tags".to_string(), - mcp_types::Tool { - name: "tags".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "tags": { "type": "array" } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Tags".to_string()), - }, + mcp_tool( + "tags", + "Tags", + serde_json::json!({ + "type": "object", + "properties": {"tags": {"type": "array"}} + }), + ), )])), &[], ) @@ -2370,20 +2352,16 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/value".to_string(), - mcp_types::Tool { - name: "value".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "value": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("AnyOf Value".to_string()), - }, + mcp_tool( + "value", + "AnyOf Value", + serde_json::json!({ + "type": "object", + "properties": { + "value": {"anyOf": [{"type": "string"}, {"type": "number"}]} + } + }), + ), )])), &[], ) @@ -2482,46 +2460,33 @@ Examples of valid command strings: &tools_config, Some(HashMap::from([( "test_server/do_something_cool".to_string(), - mcp_types::Tool { - name: "do_something_cool".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "string_argument": { - "type": "string", - }, - "number_argument": { - "type": "number", - }, + mcp_tool( + "do_something_cool", + "Do something cool", + serde_json::json!({ + "type": "object", + "properties": { + "string_argument": {"type": "string"}, + "number_argument": {"type": "number"}, "object_argument": { "type": "object", "properties": { - "string_property": { "type": "string" }, - "number_property": { "type": "number" }, + "string_property": {"type": "string"}, + "number_property": {"type": "number"} }, - "required": [ - "string_property", - "number_property", - ], + "required": ["string_property", "number_property"], "additionalProperties": { "type": "object", "properties": { - "addtl_prop": { "type": "string" }, + "addtl_prop": {"type": "string"} }, - "required": [ - "addtl_prop", - ], - "additionalProperties": false, - }, - }, - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Do something cool".to_string()), - }, + "required": ["addtl_prop"], + "additionalProperties": false + } + } + } + }), + ), )])), &[], ) diff --git a/codex-rs/core/tests/common/lib.rs b/codex-rs/core/tests/common/lib.rs index 5b80f80ba3..5df2a74edd 100644 --- a/codex-rs/core/tests/common/lib.rs +++ b/codex-rs/core/tests/common/lib.rs @@ -209,7 +209,7 @@ where use tokio::time::timeout; loop { // Allow a bit more time to accommodate async startup work (e.g. config IO, tool discovery) - let ev = timeout(wait_time.max(Duration::from_secs(5)), codex.next_event()) + let ev = timeout(wait_time.max(Duration::from_secs(10)), codex.next_event()) .await .expect("timeout waiting for event") .expect("stream ended unexpectedly"); diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 7eaeef6590..cdf6a1adc5 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -26,7 +26,6 @@ use core_test_support::skip_if_no_network; use core_test_support::stdio_server_bin; use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; -use mcp_types::ContentBlock; use serde_json::Value; use serde_json::json; use serial_test::serial; @@ -307,14 +306,10 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> { let base64_only = OPENAI_PNG .strip_prefix("data:image/png;base64,") .expect("data url prefix"); - match &result.content[0] { - ContentBlock::ImageContent(img) => { - assert_eq!(img.mime_type, "image/png"); - assert_eq!(img.r#type, "image"); - assert_eq!(img.data, base64_only); - } - other => panic!("expected image content, got {other:?}"), - } + let entry = result.content[0].as_object().expect("content object"); + assert_eq!(entry.get("type"), Some(&json!("image"))); + assert_eq!(entry.get("mimeType"), Some(&json!("image/png"))); + assert_eq!(entry.get("data"), Some(&json!(base64_only))); wait_for_event(&fixture.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; diff --git a/codex-rs/exec/Cargo.toml b/codex-rs/exec/Cargo.toml index 862d8d1694..860f4034be 100644 --- a/codex-rs/exec/Cargo.toml +++ b/codex-rs/exec/Cargo.toml @@ -28,7 +28,6 @@ codex-common = { workspace = true, features = [ codex-core = { workspace = true } codex-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } -mcp-types = { workspace = true } owo-colors = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } @@ -56,9 +55,9 @@ assert_cmd = { workspace = true } codex-utils-cargo-bin = { workspace = true } core_test_support = { workspace = true } libc = { workspace = true } -mcp-types = { workspace = true } predicates = { workspace = true } pretty_assertions = { workspace = true } +rmcp = { workspace = true } tempfile = { workspace = true } uuid = { workspace = true } walkdir = { workspace = true } diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 8469dc42ed..3b4a3bf86b 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -375,7 +375,8 @@ impl EventProcessor for EventProcessorWithHumanOutput { ts_msg!(self, "{}", title.style(title_style)); if let Ok(res) = result { - let val: serde_json::Value = res.into(); + let val = serde_json::to_value(res) + .unwrap_or_else(|_| serde_json::Value::String("".to_string())); let pretty = serde_json::to_string_pretty(&val).unwrap_or_else(|_| val.to_string()); diff --git a/codex-rs/exec/src/exec_events.rs b/codex-rs/exec/src/exec_events.rs index 47c67a6dca..368098f16b 100644 --- a/codex-rs/exec/src/exec_events.rs +++ b/codex-rs/exec/src/exec_events.rs @@ -1,5 +1,4 @@ use codex_protocol::models::WebSearchAction; -use mcp_types::ContentBlock as McpContentBlock; use serde::Deserialize; use serde::Serialize; use serde_json::Value as JsonValue; @@ -256,7 +255,14 @@ pub struct CollabToolCallItem { /// Result payload produced by an MCP tool invocation. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)] pub struct McpToolCallItemResult { - pub content: Vec, + // NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a + // more precise Rust representation of MCP content blocks. We intentionally + // use `serde_json::Value` here because this crate exports JSON schema + TS + // types (`schemars`/`ts-rs`), and the rmcp model types aren't set up to be + // schema/TS friendly (and would introduce heavier coupling to rmcp's Rust + // representations). Using `JsonValue` keeps the payload wire-shaped and + // easy to export. + pub content: Vec, pub structured_content: Option, } diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index 47b7a9f331..6a853857da 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -56,6 +56,7 @@ use codex_exec::exec_events::Usage; use codex_exec::exec_events::WebSearchItem; use codex_protocol::ThreadId; use codex_protocol::config_types::ModeKind; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::WebSearchAction; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; @@ -63,10 +64,8 @@ use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ExecCommandOutputDeltaEvent; use codex_protocol::protocol::ExecOutputStream; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; -use mcp_types::TextContent; use pretty_assertions::assert_eq; +use rmcp::model::Content; use serde_json::json; use std::path::PathBuf; use std::time::Duration; @@ -385,6 +384,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() { content: Vec::new(), is_error: None, structured_content: None, + meta: None, }), }), ); @@ -499,13 +499,10 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() { invocation, duration: Duration::from_millis(10), result: Ok(CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: "done".to_string(), - r#type: "text".to_string(), - })], + content: vec![serde_json::to_value(Content::text("done")).unwrap()], is_error: None, structured_content: Some(json!({ "status": "ok" })), + meta: None, }), }), ); @@ -520,11 +517,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() { tool: "tool_z".to_string(), arguments: serde_json::Value::Null, result: Some(McpToolCallItemResult { - content: vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: "done".to_string(), - r#type: "text".to_string(), - })], + content: vec![serde_json::to_value(Content::text("done")).unwrap()], structured_content: Some(json!({ "status": "ok" })), }), error: None, diff --git a/codex-rs/mcp-server/Cargo.toml b/codex-rs/mcp-server/Cargo.toml index 6236384c95..7a952342de 100644 --- a/codex-rs/mcp-server/Cargo.toml +++ b/codex-rs/mcp-server/Cargo.toml @@ -22,7 +22,7 @@ codex-common = { workspace = true, features = ["cli"] } codex-core = { workspace = true } codex-protocol = { workspace = true } codex-utils-json-to-toml = { workspace = true } -mcp-types = { workspace = true } +rmcp = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index 8131d7da52..94bf4369a9 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -6,15 +6,15 @@ use codex_core::protocol::AskForApproval; use codex_protocol::ThreadId; use codex_protocol::config_types::SandboxMode; use codex_utils_json_to_toml::json_to_toml; -use mcp_types::Tool; -use mcp_types::ToolInputSchema; -use mcp_types::ToolOutputSchema; +use rmcp::model::JsonObject; +use rmcp::model::Tool; use schemars::JsonSchema; use schemars::r#gen::SchemaSettings; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; use std::path::PathBuf; +use std::sync::Arc; /// Client-supplied configuration for a `codex` tool-call. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)] @@ -115,35 +115,35 @@ pub(crate) fn create_tool_for_codex_tool_call_param() -> Tool { .into_generator() .into_root_schema_for::(); - #[expect(clippy::expect_used)] - let schema_value = - serde_json::to_value(&schema).expect("Codex tool schema should serialise to JSON"); - - let tool_input_schema = - serde_json::from_value::(schema_value).unwrap_or_else(|e| { - panic!("failed to create Tool from schema: {e}"); - }); + let input_schema = create_tool_input_schema(schema, "Codex tool schema should serialize"); Tool { - name: "codex".to_string(), + name: "codex".into(), title: Some("Codex".to_string()), - input_schema: tool_input_schema, + input_schema, output_schema: Some(codex_tool_output_schema()), description: Some( - "Run a Codex session. Accepts configuration parameters matching the Codex Config struct.".to_string(), + "Run a Codex session. Accepts configuration parameters matching the Codex Config struct." + .into(), ), annotations: None, + icons: None, + meta: None, } } -fn codex_tool_output_schema() -> ToolOutputSchema { - ToolOutputSchema { - properties: Some(serde_json::json!({ +fn codex_tool_output_schema() -> Arc { + let schema = serde_json::json!({ + "type": "object", + "properties": { "threadId": { "type": "string" }, "content": { "type": "string" } - })), - required: Some(vec!["threadId".to_string(), "content".to_string()]), - r#type: "object".to_string(), + }, + "required": ["threadId", "content"], + }); + match schema { + serde_json::Value::Object(map) => Arc::new(map), + _ => unreachable!("json literal must be an object"), } } @@ -237,27 +237,46 @@ pub(crate) fn create_tool_for_codex_tool_call_reply_param() -> Tool { .into_generator() .into_root_schema_for::(); - #[expect(clippy::expect_used)] - let schema_value = - serde_json::to_value(&schema).expect("Codex reply tool schema should serialise to JSON"); - - let tool_input_schema = - serde_json::from_value::(schema_value).unwrap_or_else(|e| { - panic!("failed to create Tool from schema: {e}"); - }); + let input_schema = create_tool_input_schema(schema, "Codex reply tool schema should serialize"); Tool { - name: "codex-reply".to_string(), + name: "codex-reply".into(), title: Some("Codex Reply".to_string()), - input_schema: tool_input_schema, + input_schema, output_schema: Some(codex_tool_output_schema()), description: Some( - "Continue a Codex conversation by providing the thread id and prompt.".to_string(), + "Continue a Codex conversation by providing the thread id and prompt.".into(), ), annotations: None, + icons: None, + meta: None, } } +fn create_tool_input_schema( + schema: schemars::schema::RootSchema, + panic_message: &str, +) -> Arc { + #[expect(clippy::expect_used)] + let schema_value = serde_json::to_value(&schema).expect(panic_message); + let mut schema_object = match schema_value { + serde_json::Value::Object(object) => object, + _ => panic!("tool schema should serialize to a JSON object"), + }; + + // Prefer keeping the "core" JSON Schema keys while still preserving `$defs` + // in case any `$ref` leaks into the generated schema (even though we try + // to inline subschemas). + let mut input_schema = JsonObject::new(); + for key in ["properties", "required", "type", "$defs", "definitions"] { + if let Some(value) = schema_object.remove(key) { + input_schema.insert(key.to_string(), value); + } + } + + Arc::new(input_schema) +} + #[cfg(test)] mod tests { use super::*; diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 1dbdbc767b..6273607a22 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -23,15 +23,12 @@ use codex_core::protocol::Submission; use codex_core::protocol::TurnCompleteEvent; use codex_protocol::ThreadId; use codex_protocol::user_input::UserInput; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; -use mcp_types::RequestId; -use mcp_types::TextContent; +use rmcp::model::CallToolResult; +use rmcp::model::Content; +use rmcp::model::RequestId; use serde_json::json; use tokio::sync::Mutex; -pub(crate) const INVALID_PARAMS_ERROR_CODE: i64 = -32602; - /// To adhere to MCP `tools/call` response format, include the Codex /// `threadId` in the `structured_content` field of the response. /// Some MCP clients ignore `content` when `structuredContent` is present, so @@ -42,11 +39,7 @@ pub(crate) fn create_call_tool_result_with_thread_id( is_error: Option, ) -> CallToolResult { let content_text = text; - let content = vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: content_text.clone(), - annotations: None, - })]; + let content = vec![Content::text(content_text.clone())]; let structured_content = json!({ "threadId": thread_id, "content": content_text, @@ -55,6 +48,7 @@ pub(crate) fn create_call_tool_result_with_thread_id( content, is_error, structured_content: Some(structured_content), + meta: None, } } @@ -78,13 +72,10 @@ pub async fn run_codex_tool_session( Ok(res) => res, Err(e) => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: format!("Failed to start Codex session: {e}"), - annotations: None, - })], + content: vec![Content::text(format!("Failed to start Codex session: {e}"))], is_error: Some(true), structured_content: None, + meta: None, }; outgoing.send_response(id.clone(), result).await; return; @@ -109,10 +100,7 @@ pub async fn run_codex_tool_session( // Use the original MCP request ID as the `sub_id` for the Codex submission so that // any events emitted for this tool-call can be correlated with the // originating `tools/call` request. - let sub_id = match &id { - RequestId::String(s) => s.clone(), - RequestId::Integer(n) => n.to_string(), - }; + let sub_id = id.to_string(); running_requests_id_to_codex_uuid .lock() .await @@ -207,10 +195,7 @@ async fn run_codex_tool_session_inner( request_id: RequestId, running_requests_id_to_codex_uuid: Arc>>, ) { - let request_id_str = match &request_id { - RequestId::String(s) => s.clone(), - RequestId::Integer(n) => n.to_string(), - }; + let request_id_str = request_id.to_string(); // Stream events until the task needs to pause for user interaction or // completes. diff --git a/codex-rs/mcp-server/src/error_code.rs b/codex-rs/mcp-server/src/error_code.rs deleted file mode 100644 index 1ffd889d40..0000000000 --- a/codex-rs/mcp-server/src/error_code.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) const INVALID_REQUEST_ERROR_CODE: i64 = -32600; -pub(crate) const INTERNAL_ERROR_CODE: i64 = -32603; diff --git a/codex-rs/mcp-server/src/exec_approval.rs b/codex-rs/mcp-server/src/exec_approval.rs index a98099dcf2..c7914d1e60 100644 --- a/codex-rs/mcp-server/src/exec_approval.rs +++ b/codex-rs/mcp-server/src/exec_approval.rs @@ -6,20 +6,16 @@ use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; use codex_protocol::ThreadId; use codex_protocol::parse_command::ParsedCommand; -use mcp_types::ElicitRequest; -use mcp_types::ElicitRequestParamsRequestedSchema; -use mcp_types::JSONRPCErrorError; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; +use rmcp::model::ErrorData; +use rmcp::model::RequestId; use serde::Deserialize; use serde::Serialize; +use serde_json::Value; use serde_json::json; use tracing::error; -use crate::codex_tool_runner::INVALID_PARAMS_ERROR_CODE; - -/// Conforms to [`mcp_types::ElicitRequestParams`] so that it can be used as the -/// `params` field of an [`ElicitRequest`]. +/// Conforms to the MCP elicitation request params shape, so it can be used as +/// the `params` field of an `elicitation/create` request. #[derive(Debug, Deserialize, Serialize)] pub struct ExecApprovalElicitRequestParams { // These fields are required so that `params` @@ -27,7 +23,7 @@ pub struct ExecApprovalElicitRequestParams { pub message: String, #[serde(rename = "requestedSchema")] - pub requested_schema: ElicitRequestParamsRequestedSchema, + pub requested_schema: Value, // These are additional fields the client can use to // correlate the request with the codex tool call. @@ -73,11 +69,7 @@ pub(crate) async fn handle_exec_approval_request( let params = ExecApprovalElicitRequestParams { message, - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, + requested_schema: json!({"type":"object","properties":{}}), thread_id, codex_elicitation: "exec-approval".to_string(), codex_mcp_tool_call_id: tool_call_id.clone(), @@ -94,14 +86,7 @@ pub(crate) async fn handle_exec_approval_request( error!("{message}"); outgoing - .send_error( - request_id.clone(), - JSONRPCErrorError { - code: INVALID_PARAMS_ERROR_CODE, - message, - data: None, - }, - ) + .send_error(request_id.clone(), ErrorData::invalid_params(message, None)) .await; return; @@ -109,7 +94,7 @@ pub(crate) async fn handle_exec_approval_request( }; let on_response = outgoing - .send_request(ElicitRequest::METHOD, Some(params_json)) + .send_request("elicitation/create", Some(params_json)) .await; // Listen for the response on a separate task so we don't block the main agent loop. @@ -124,7 +109,7 @@ pub(crate) async fn handle_exec_approval_request( async fn on_exec_approval_response( event_id: String, - receiver: tokio::sync::oneshot::Receiver, + receiver: tokio::sync::oneshot::Receiver, codex: Arc, ) { let response = receiver.await; diff --git a/codex-rs/mcp-server/src/lib.rs b/codex-rs/mcp-server/src/lib.rs index dabd7cca0f..eed176c575 100644 --- a/codex-rs/mcp-server/src/lib.rs +++ b/codex-rs/mcp-server/src/lib.rs @@ -8,7 +8,10 @@ use std::path::PathBuf; use codex_common::CliConfigOverrides; use codex_core::config::Config; -use mcp_types::JSONRPCMessage; +use rmcp::model::ClientNotification; +use rmcp::model::ClientRequest; +use rmcp::model::JsonRpcMessage; +use serde_json::Value; use tokio::io::AsyncBufReadExt; use tokio::io::AsyncWriteExt; use tokio::io::BufReader; @@ -21,13 +24,13 @@ use tracing_subscriber::EnvFilter; mod codex_tool_config; mod codex_tool_runner; -mod error_code; mod exec_approval; pub(crate) mod message_processor; mod outgoing_message; mod patch_approval; use crate::message_processor::MessageProcessor; +use crate::outgoing_message::OutgoingJsonRpcMessage; use crate::outgoing_message::OutgoingMessage; use crate::outgoing_message::OutgoingMessageSender; @@ -43,6 +46,8 @@ pub use crate::patch_approval::PatchApprovalResponse; /// plenty for an interactive CLI. const CHANNEL_CAPACITY: usize = 128; +type IncomingMessage = JsonRpcMessage; + pub async fn run_main( codex_linux_sandbox_exe: Option, cli_config_overrides: CliConfigOverrides, @@ -55,7 +60,7 @@ pub async fn run_main( .init(); // Set up channels. - let (incoming_tx, mut incoming_rx) = mpsc::channel::(CHANNEL_CAPACITY); + let (incoming_tx, mut incoming_rx) = mpsc::channel::(CHANNEL_CAPACITY); let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded_channel::(); // Task: read from stdin, push to `incoming_tx`. @@ -66,14 +71,14 @@ pub async fn run_main( let mut lines = reader.lines(); while let Some(line) = lines.next_line().await.unwrap_or_default() { - match serde_json::from_str::(&line) { + match serde_json::from_str::(&line) { Ok(msg) => { if incoming_tx.send(msg).await.is_err() { // Receiver gone – nothing left to do. break; } } - Err(e) => error!("Failed to deserialize JSONRPCMessage: {e}"), + Err(e) => error!("Failed to deserialize JSON-RPC message: {e}"), } } @@ -106,10 +111,10 @@ pub async fn run_main( async move { while let Some(msg) = incoming_rx.recv().await { match msg { - JSONRPCMessage::Request(r) => processor.process_request(r).await, - JSONRPCMessage::Response(r) => processor.process_response(r).await, - JSONRPCMessage::Notification(n) => processor.process_notification(n).await, - JSONRPCMessage::Error(e) => processor.process_error(e), + JsonRpcMessage::Request(r) => processor.process_request(r).await, + JsonRpcMessage::Response(r) => processor.process_response(r).await, + JsonRpcMessage::Notification(n) => processor.process_notification(n).await, + JsonRpcMessage::Error(e) => processor.process_error(e), } } @@ -121,7 +126,7 @@ pub async fn run_main( let stdout_writer_handle = tokio::spawn(async move { let mut stdout = io::stdout(); while let Some(outgoing_message) = outgoing_rx.recv().await { - let msg: JSONRPCMessage = outgoing_message.into(); + let msg: OutgoingJsonRpcMessage = outgoing_message.into(); match serde_json::to_string(&msg) { Ok(json) => { if let Err(e) = stdout.write_all(json.as_bytes()).await { @@ -133,7 +138,7 @@ pub async fn run_main( break; } } - Err(e) => error!("Failed to serialize JSONRPCMessage: {e}"), + Err(e) => error!("Failed to serialize JSON-RPC message: {e}"), } } diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index 9d947cda3f..1d9ab192aa 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -1,41 +1,40 @@ use std::collections::HashMap; use std::path::PathBuf; -use crate::codex_tool_config::CodexToolCallParam; -use crate::codex_tool_config::CodexToolCallReplyParam; -use crate::codex_tool_config::create_tool_for_codex_tool_call_param; -use crate::codex_tool_config::create_tool_for_codex_tool_call_reply_param; -use crate::error_code::INVALID_REQUEST_ERROR_CODE; -use crate::outgoing_message::OutgoingMessageSender; -use codex_protocol::ThreadId; -use codex_protocol::protocol::SessionSource; - use codex_core::AuthManager; use codex_core::ThreadManager; use codex_core::config::Config; use codex_core::default_client::USER_AGENT_SUFFIX; use codex_core::default_client::get_codex_user_agent; use codex_core::protocol::Submission; -use mcp_types::CallToolRequestParams; -use mcp_types::CallToolResult; -use mcp_types::ClientRequest as McpClientRequest; -use mcp_types::ContentBlock; -use mcp_types::JSONRPCError; -use mcp_types::JSONRPCErrorError; -use mcp_types::JSONRPCNotification; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::ListToolsResult; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; -use mcp_types::ServerCapabilitiesTools; -use mcp_types::ServerNotification; -use mcp_types::TextContent; +use codex_protocol::ThreadId; +use codex_protocol::protocol::SessionSource; +use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolResult; +use rmcp::model::ClientNotification; +use rmcp::model::ClientRequest; +use rmcp::model::ErrorCode; +use rmcp::model::ErrorData; +use rmcp::model::Implementation; +use rmcp::model::InitializeResult; +use rmcp::model::JsonRpcError; +use rmcp::model::JsonRpcNotification; +use rmcp::model::JsonRpcRequest; +use rmcp::model::JsonRpcResponse; +use rmcp::model::RequestId; +use rmcp::model::ServerCapabilities; +use rmcp::model::ToolsCapability; use serde_json::json; use std::sync::Arc; use tokio::sync::Mutex; use tokio::task; +use crate::codex_tool_config::CodexToolCallParam; +use crate::codex_tool_config::CodexToolCallReplyParam; +use crate::codex_tool_config::create_tool_for_codex_tool_call_param; +use crate::codex_tool_config::create_tool_for_codex_tool_call_reply_param; +use crate::outgoing_message::OutgoingMessageSender; + pub(crate) struct MessageProcessor { outgoing: Arc, initialized: bool, @@ -72,126 +71,113 @@ impl MessageProcessor { } } - pub(crate) async fn process_request(&mut self, request: JSONRPCRequest) { - // Hold on to the ID so we can respond. + pub(crate) async fn process_request(&mut self, request: JsonRpcRequest) { let request_id = request.id.clone(); + let client_request = request.request; - let client_request = match McpClientRequest::try_from(request) { - Ok(client_request) => client_request, - Err(e) => { - tracing::warn!("Failed to convert request: {e}"); - return; - } - }; - - // Dispatch to a dedicated handler for each request type. match client_request { - McpClientRequest::InitializeRequest(params) => { - self.handle_initialize(request_id, params).await; + ClientRequest::InitializeRequest(params) => { + self.handle_initialize(request_id, params.params).await; } - McpClientRequest::PingRequest(params) => { - self.handle_ping(request_id, params).await; + ClientRequest::PingRequest(_params) => { + self.handle_ping(request_id).await; } - McpClientRequest::ListResourcesRequest(params) => { - self.handle_list_resources(params); + ClientRequest::ListResourcesRequest(params) => { + self.handle_list_resources(params.params); } - McpClientRequest::ListResourceTemplatesRequest(params) => { - self.handle_list_resource_templates(params); + ClientRequest::ListResourceTemplatesRequest(params) => { + self.handle_list_resource_templates(params.params); } - McpClientRequest::ReadResourceRequest(params) => { - self.handle_read_resource(params); + ClientRequest::ReadResourceRequest(params) => { + self.handle_read_resource(params.params); } - McpClientRequest::SubscribeRequest(params) => { - self.handle_subscribe(params); + ClientRequest::SubscribeRequest(params) => { + self.handle_subscribe(params.params); } - McpClientRequest::UnsubscribeRequest(params) => { - self.handle_unsubscribe(params); + ClientRequest::UnsubscribeRequest(params) => { + self.handle_unsubscribe(params.params); } - McpClientRequest::ListPromptsRequest(params) => { - self.handle_list_prompts(params); + ClientRequest::ListPromptsRequest(params) => { + self.handle_list_prompts(params.params); } - McpClientRequest::GetPromptRequest(params) => { - self.handle_get_prompt(params); + ClientRequest::GetPromptRequest(params) => { + self.handle_get_prompt(params.params); } - McpClientRequest::ListToolsRequest(params) => { - self.handle_list_tools(request_id, params).await; + ClientRequest::ListToolsRequest(params) => { + self.handle_list_tools(request_id, params.params).await; } - McpClientRequest::CallToolRequest(params) => { - self.handle_call_tool(request_id, params).await; + ClientRequest::CallToolRequest(params) => { + self.handle_call_tool(request_id, params.params).await; } - McpClientRequest::SetLevelRequest(params) => { - self.handle_set_level(params); + ClientRequest::SetLevelRequest(params) => { + self.handle_set_level(params.params); } - McpClientRequest::CompleteRequest(params) => { - self.handle_complete(params); + ClientRequest::CompleteRequest(params) => { + self.handle_complete(params.params); + } + ClientRequest::CustomRequest(custom) => { + let method = custom.method.clone(); + self.outgoing + .send_error( + request_id, + ErrorData::new( + ErrorCode::METHOD_NOT_FOUND, + format!("method not found: {method}"), + Some(json!({ "method": method })), + ), + ) + .await; } } } - /// Handle a standalone JSON-RPC response originating from the peer. - pub(crate) async fn process_response(&mut self, response: JSONRPCResponse) { + pub(crate) async fn process_response(&mut self, response: JsonRpcResponse) { tracing::info!("<- response: {:?}", response); - let JSONRPCResponse { id, result, .. } = response; + let JsonRpcResponse { id, result, .. } = response; self.outgoing.notify_client_response(id, result).await } - /// Handle a fire-and-forget JSON-RPC notification. - pub(crate) async fn process_notification(&mut self, notification: JSONRPCNotification) { - let server_notification = match ServerNotification::try_from(notification) { - Ok(n) => n, - Err(e) => { - tracing::warn!("Failed to convert notification: {e}"); - return; + pub(crate) async fn process_notification( + &mut self, + notification: JsonRpcNotification, + ) { + match notification.notification { + ClientNotification::CancelledNotification(params) => { + self.handle_cancelled_notification(params.params).await; } - }; - - // Similar to requests, route each notification type to its own stub - // handler so additional logic can be implemented incrementally. - match server_notification { - ServerNotification::CancelledNotification(params) => { - self.handle_cancelled_notification(params).await; + ClientNotification::ProgressNotification(params) => { + self.handle_progress_notification(params.params); } - ServerNotification::ProgressNotification(params) => { - self.handle_progress_notification(params); + ClientNotification::RootsListChangedNotification(_params) => { + self.handle_roots_list_changed(); } - ServerNotification::ResourceListChangedNotification(params) => { - self.handle_resource_list_changed(params); + ClientNotification::InitializedNotification(_) => { + self.handle_initialized_notification(); } - ServerNotification::ResourceUpdatedNotification(params) => { - self.handle_resource_updated(params); - } - ServerNotification::PromptListChangedNotification(params) => { - self.handle_prompt_list_changed(params); - } - ServerNotification::ToolListChangedNotification(params) => { - self.handle_tool_list_changed(params); - } - ServerNotification::LoggingMessageNotification(params) => { - self.handle_logging_message(params); + ClientNotification::CustomNotification(_) => { + tracing::warn!("ignoring custom client notification"); } } } - /// Handle an error object received from the peer. - pub(crate) fn process_error(&mut self, err: JSONRPCError) { + pub(crate) fn process_error(&mut self, err: JsonRpcError) { tracing::error!("<- error: {:?}", err); } async fn handle_initialize( &mut self, id: RequestId, - params: ::Params, + params: rmcp::model::InitializeRequestParam, ) { tracing::info!("initialize -> params: {:?}", params); if self.initialized { - // Already initialised: send JSON-RPC error response. - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: "initialize called more than once".to_string(), - data: None, - }; - self.outgoing.send_error(id, error).await; + self.outgoing + .send_error( + id, + ErrorData::invalid_request("initialize called more than once", None), + ) + .await; return; } @@ -203,109 +189,109 @@ impl MessageProcessor { *suffix = Some(user_agent_suffix); } - self.initialized = true; + let server_info = Implementation { + name: "codex-mcp-server".to_string(), + title: Some("Codex".to_string()), + version: env!("CARGO_PKG_VERSION").to_string(), + icons: None, + website_url: None, + }; - // Build a minimal InitializeResult. Fill with placeholders. - let result = mcp_types::InitializeResult { - capabilities: mcp_types::ServerCapabilities { - completions: None, - experimental: None, - logging: None, - prompts: None, - resources: None, - tools: Some(ServerCapabilitiesTools { + // Preserve Codex's existing non-spec `serverInfo.user_agent` field. + let mut server_info_value = match serde_json::to_value(&server_info) { + Ok(value) => value, + Err(err) => { + self.outgoing + .send_error( + id, + ErrorData::internal_error( + format!("failed to serialize server info: {err}"), + None, + ), + ) + .await; + return; + } + }; + if let serde_json::Value::Object(ref mut obj) = server_info_value { + obj.insert("user_agent".to_string(), json!(get_codex_user_agent())); + } + + let mut result_value = match serde_json::to_value(InitializeResult { + capabilities: ServerCapabilities { + tools: Some(ToolsCapability { list_changed: Some(true), }), + ..Default::default() }, instructions: None, protocol_version: params.protocol_version.clone(), - server_info: mcp_types::Implementation { - name: "codex-mcp-server".to_string(), - version: env!("CARGO_PKG_VERSION").to_string(), - title: Some("Codex".to_string()), - user_agent: Some(get_codex_user_agent()), - }, + server_info, + }) { + Ok(value) => value, + Err(err) => { + self.outgoing + .send_error( + id, + ErrorData::internal_error( + format!("failed to serialize initialize response: {err}"), + None, + ), + ) + .await; + return; + } }; - self.send_response::(id, result) - .await; + if let serde_json::Value::Object(ref mut obj) = result_value { + obj.insert("serverInfo".to_string(), server_info_value); + } + + self.initialized = true; + self.outgoing.send_response(id, result_value).await; } - async fn send_response(&self, id: RequestId, result: T::Result) - where - T: ModelContextProtocolRequest, - { - self.outgoing.send_response(id, result).await; + async fn handle_ping(&self, id: RequestId) { + tracing::info!("ping"); + self.outgoing.send_response(id, json!({})).await; } - async fn handle_ping( - &self, - id: RequestId, - params: ::Params, - ) { - tracing::info!("ping -> params: {:?}", params); - let result = json!({}); - self.send_response::(id, result) - .await; - } - - fn handle_list_resources( - &self, - params: ::Params, - ) { + fn handle_list_resources(&self, params: Option) { tracing::info!("resources/list -> params: {:?}", params); } - fn handle_list_resource_templates( - &self, - params: - ::Params, - ) { + fn handle_list_resource_templates(&self, params: Option) { tracing::info!("resources/templates/list -> params: {:?}", params); } - fn handle_read_resource( - &self, - params: ::Params, - ) { + fn handle_read_resource(&self, params: rmcp::model::ReadResourceRequestParam) { tracing::info!("resources/read -> params: {:?}", params); } - fn handle_subscribe( - &self, - params: ::Params, - ) { + fn handle_subscribe(&self, params: rmcp::model::SubscribeRequestParam) { tracing::info!("resources/subscribe -> params: {:?}", params); } - fn handle_unsubscribe( - &self, - params: ::Params, - ) { + fn handle_unsubscribe(&self, params: rmcp::model::UnsubscribeRequestParam) { tracing::info!("resources/unsubscribe -> params: {:?}", params); } - fn handle_list_prompts( - &self, - params: ::Params, - ) { + fn handle_list_prompts(&self, params: Option) { tracing::info!("prompts/list -> params: {:?}", params); } - fn handle_get_prompt( - &self, - params: ::Params, - ) { + fn handle_get_prompt(&self, params: rmcp::model::GetPromptRequestParam) { tracing::info!("prompts/get -> params: {:?}", params); } async fn handle_list_tools( &self, id: RequestId, - params: ::Params, + params: Option, ) { tracing::trace!("tools/list -> {params:?}"); - let result = ListToolsResult { + let result = rmcp::model::ListToolsResult { + meta: None, tools: vec![ create_tool_for_codex_tool_call_param(), create_tool_for_codex_tool_call_reply_param(), @@ -313,19 +299,14 @@ impl MessageProcessor { next_cursor: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; } - async fn handle_call_tool( - &self, - id: RequestId, - params: ::Params, - ) { + async fn handle_call_tool(&self, id: RequestId, params: CallToolRequestParam) { tracing::info!("tools/call -> params: {:?}", params); - let CallToolRequestParams { name, arguments } = params; + let CallToolRequestParam { name, arguments } = params; - match name.as_str() { + match name.as_ref() { "codex" => self.handle_tool_call_codex(id, arguments).await, "codex-reply" => { self.handle_tool_call_codex_session_reply(id, arguments) @@ -333,20 +314,22 @@ impl MessageProcessor { } _ => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: format!("Unknown tool '{name}'"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!("Unknown tool '{name}'"))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; } } } - async fn handle_tool_call_codex(&self, id: RequestId, arguments: Option) { + + async fn handle_tool_call_codex( + &self, + id: RequestId, + arguments: Option, + ) { + let arguments = arguments.map(serde_json::Value::Object); let (initial_prompt, config): (String, Config) = match arguments { Some(json_val) => match serde_json::from_value::(json_val) { Ok(tool_cfg) => match tool_cfg @@ -356,50 +339,40 @@ impl MessageProcessor { Ok(cfg) => cfg, Err(e) => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!( - "Failed to load Codex configuration from overrides: {e}" - ), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to load Codex configuration from overrides: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; return; } }, Err(e) => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!("Failed to parse configuration for Codex tool: {e}"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to parse configuration for Codex tool: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; return; } }, None => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: - "Missing arguments for codex tool-call; the `prompt` field is required." - .to_string(), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text( + "Missing arguments for codex tool-call; the `prompt` field is required.", + )], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; return; } }; @@ -428,8 +401,9 @@ impl MessageProcessor { async fn handle_tool_call_codex_session_reply( &self, request_id: RequestId, - arguments: Option, + arguments: Option, ) { + let arguments = arguments.map(serde_json::Value::Object); tracing::info!("tools/call -> params: {:?}", arguments); // parse arguments @@ -439,16 +413,14 @@ impl MessageProcessor { Err(e) => { tracing::error!("Failed to parse Codex tool call reply parameters: {e}"); let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!("Failed to parse configuration for Codex tool: {e}"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to parse configuration for Codex tool: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(request_id, result) - .await; + self.outgoing.send_response(request_id, result).await; return; } }, @@ -457,16 +429,14 @@ impl MessageProcessor { "Missing arguments for codex-reply tool-call; the `thread_id` and `prompt` fields are required." ); let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: "Missing arguments for codex-reply tool-call; the `thread_id` and `prompt` fields are required.".to_owned(), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text( + "Missing arguments for codex-reply tool-call; the `thread_id` and `prompt` fields are required.", + )], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(request_id, result) - .await; + self.outgoing.send_response(request_id, result).await; return; } }; @@ -476,16 +446,14 @@ impl MessageProcessor { Err(e) => { tracing::error!("Failed to parse thread_id: {e}"); let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!("Failed to parse thread_id: {e}"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to parse thread_id: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(request_id, result) - .await; + self.outgoing.send_response(request_id, result).await; return; } }; @@ -528,17 +496,11 @@ impl MessageProcessor { }); } - fn handle_set_level( - &self, - params: ::Params, - ) { + fn handle_set_level(&self, params: rmcp::model::SetLevelRequestParam) { tracing::info!("logging/setLevel -> params: {:?}", params); } - fn handle_complete( - &self, - params: ::Params, - ) { + fn handle_complete(&self, params: rmcp::model::CompleteRequestParam) { tracing::info!("completion/complete -> params: {:?}", params); } @@ -546,16 +508,10 @@ impl MessageProcessor { // Notification handlers // --------------------------------------------------------------------- - async fn handle_cancelled_notification( - &self, - params: ::Params, - ) { + async fn handle_cancelled_notification(&self, params: rmcp::model::CancelledNotificationParam) { let request_id = params.request_id; // Create a stable string form early for logging and submission id. - let request_id_string = match &request_id { - RequestId::String(s) => s.clone(), - RequestId::Integer(i) => i.to_string(), - }; + let request_id_string = request_id.to_string(); // Obtain the thread id while holding the first lock, then release. let thread_id = { @@ -563,7 +519,7 @@ impl MessageProcessor { match map_guard.get(&request_id) { Some(id) => *id, None => { - tracing::warn!("Session not found for request_id: {}", request_id_string); + tracing::warn!("Session not found for request_id: {request_id_string}"); return; } } @@ -580,13 +536,13 @@ impl MessageProcessor { }; // Submit interrupt to Codex. - let err = codex_arc + if let Err(e) = codex_arc .submit_with_id(Submission { id: request_id_string, op: codex_core::protocol::Op::Interrupt, }) - .await; - if let Err(e) = err { + .await + { tracing::error!("Failed to submit interrupt to Codex: {e}"); return; } @@ -597,48 +553,15 @@ impl MessageProcessor { .remove(&request_id); } - fn handle_progress_notification( - &self, - params: ::Params, - ) { + fn handle_progress_notification(&self, params: rmcp::model::ProgressNotificationParam) { tracing::info!("notifications/progress -> params: {:?}", params); } - fn handle_resource_list_changed( - &self, - params: ::Params, - ) { - tracing::info!( - "notifications/resources/list_changed -> params: {:?}", - params - ); + fn handle_roots_list_changed(&self) { + tracing::info!("notifications/roots/list_changed"); } - fn handle_resource_updated( - &self, - params: ::Params, - ) { - tracing::info!("notifications/resources/updated -> params: {:?}", params); - } - - fn handle_prompt_list_changed( - &self, - params: ::Params, - ) { - tracing::info!("notifications/prompts/list_changed -> params: {:?}", params); - } - - fn handle_tool_list_changed( - &self, - params: ::Params, - ) { - tracing::info!("notifications/tools/list_changed -> params: {:?}", params); - } - - fn handle_logging_message( - &self, - params: ::Params, - ) { - tracing::info!("notifications/message -> params: {:?}", params); + fn handle_initialized_notification(&self) { + tracing::info!("notifications/initialized"); } } diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index 954e50d9de..e512eedbd7 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -4,28 +4,30 @@ use std::sync::atomic::Ordering; use codex_core::protocol::Event; use codex_protocol::ThreadId; -use mcp_types::JSONRPC_VERSION; -use mcp_types::JSONRPCError; -use mcp_types::JSONRPCErrorError; -use mcp_types::JSONRPCMessage; -use mcp_types::JSONRPCNotification; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::RequestId; -use mcp_types::Result; +use rmcp::model::CustomNotification; +use rmcp::model::CustomRequest; +use rmcp::model::ErrorData; +use rmcp::model::JsonRpcError; +use rmcp::model::JsonRpcMessage; +use rmcp::model::JsonRpcNotification; +use rmcp::model::JsonRpcRequest; +use rmcp::model::JsonRpcResponse; +use rmcp::model::JsonRpcVersion2_0; +use rmcp::model::RequestId; use serde::Serialize; +use serde_json::Value; use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::oneshot; use tracing::warn; -use crate::error_code::INTERNAL_ERROR_CODE; +pub(crate) type OutgoingJsonRpcMessage = JsonRpcMessage; /// Sends messages to the client and manages request callbacks. pub(crate) struct OutgoingMessageSender { next_request_id: AtomicI64, sender: mpsc::UnboundedSender, - request_id_to_callback: Mutex>>, + request_id_to_callback: Mutex>>, } impl OutgoingMessageSender { @@ -41,8 +43,8 @@ impl OutgoingMessageSender { &self, method: &str, params: Option, - ) -> oneshot::Receiver { - let id = RequestId::Integer(self.next_request_id.fetch_add(1, Ordering::Relaxed)); + ) -> oneshot::Receiver { + let id = RequestId::Number(self.next_request_id.fetch_add(1, Ordering::Relaxed)); let outgoing_message_id = id.clone(); let (tx_approve, rx_approve) = oneshot::channel(); { @@ -59,7 +61,7 @@ impl OutgoingMessageSender { rx_approve } - pub(crate) async fn notify_client_response(&self, id: RequestId, result: Result) { + pub(crate) async fn notify_client_response(&self, id: RequestId, result: Value) { let entry = { let mut request_id_to_callback = self.request_id_to_callback.lock().await; request_id_to_callback.remove_entry(&id) @@ -78,23 +80,20 @@ impl OutgoingMessageSender { } pub(crate) async fn send_response(&self, id: RequestId, response: T) { - match serde_json::to_value(response) { - Ok(result) => { - let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result }); - let _ = self.sender.send(outgoing_message); - } + let result = match serde_json::to_value(response) { + Ok(result) => result, Err(err) => { self.send_error( id, - JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("failed to serialize response: {err}"), - data: None, - }, + ErrorData::internal_error(format!("failed to serialize response: {err}"), None), ) .await; + return; } - } + }; + + let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result }); + let _ = self.sender.send(outgoing_message); } /// This is used with the MCP server, but not the more general JSON-RPC app @@ -130,7 +129,7 @@ impl OutgoingMessageSender { let _ = self.sender.send(outgoing_message); } - pub(crate) async fn send_error(&self, id: RequestId, error: JSONRPCErrorError) { + pub(crate) async fn send_error(&self, id: RequestId, error: ErrorData) { let outgoing_message = OutgoingMessage::Error(OutgoingError { id, error }); let _ = self.sender.send(outgoing_message); } @@ -144,34 +143,32 @@ pub(crate) enum OutgoingMessage { Error(OutgoingError), } -impl From for JSONRPCMessage { +impl From for OutgoingJsonRpcMessage { fn from(val: OutgoingMessage) -> Self { use OutgoingMessage::*; match val { Request(OutgoingRequest { id, method, params }) => { - JSONRPCMessage::Request(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), + JsonRpcMessage::Request(JsonRpcRequest { + jsonrpc: JsonRpcVersion2_0, id, - method, - params, + request: CustomRequest::new(method, params), }) } Notification(OutgoingNotification { method, params }) => { - JSONRPCMessage::Notification(JSONRPCNotification { - jsonrpc: JSONRPC_VERSION.into(), - method, - params, + JsonRpcMessage::Notification(JsonRpcNotification { + jsonrpc: JsonRpcVersion2_0, + notification: CustomNotification::new(method, params), }) } Response(OutgoingResponse { id, result }) => { - JSONRPCMessage::Response(JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), + JsonRpcMessage::Response(JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, id, result, }) } - Error(OutgoingError { id, error }) => JSONRPCMessage::Error(JSONRPCError { - jsonrpc: JSONRPC_VERSION.into(), + Error(OutgoingError { id, error }) => JsonRpcMessage::Error(JsonRpcError { + jsonrpc: JsonRpcVersion2_0, id, error, }), @@ -220,12 +217,12 @@ pub(crate) struct OutgoingNotificationMeta { #[derive(Debug, Clone, PartialEq, Serialize)] pub(crate) struct OutgoingResponse { pub id: RequestId, - pub result: Result, + pub result: Value, } #[derive(Debug, Clone, PartialEq, Serialize)] pub(crate) struct OutgoingError { - pub error: JSONRPCErrorError, + pub error: ErrorData, pub id: RequestId, } @@ -246,6 +243,48 @@ mod tests { use super::*; + #[test] + fn outgoing_request_serializes_as_jsonrpc_request() { + let msg: OutgoingJsonRpcMessage = OutgoingMessage::Request(OutgoingRequest { + id: RequestId::Number(1), + method: "elicitation/create".to_string(), + params: Some(json!({ "k": "v" })), + }) + .into(); + + let value = serde_json::to_value(msg).expect("message should serialize"); + let obj = value.as_object().expect("json object"); + + assert_eq!(obj.get("jsonrpc"), Some(&json!("2.0"))); + assert_eq!(obj.get("id"), Some(&json!(1))); + assert_eq!(obj.get("method"), Some(&json!("elicitation/create"))); + assert_eq!(obj.get("params"), Some(&json!({ "k": "v" }))); + assert!( + obj.get("request").is_none(), + "rmcp request must flatten to JSON-RPC method/params" + ); + } + + #[test] + fn outgoing_notification_serializes_as_jsonrpc_notification() { + let msg: OutgoingJsonRpcMessage = OutgoingMessage::Notification(OutgoingNotification { + method: "notifications/initialized".to_string(), + params: None, + }) + .into(); + + let value = serde_json::to_value(msg).expect("message should serialize"); + let obj = value.as_object().expect("json object"); + + assert_eq!(obj.get("jsonrpc"), Some(&json!("2.0"))); + assert_eq!(obj.get("method"), Some(&json!("notifications/initialized"))); + assert_eq!(obj.get("params"), Some(&serde_json::Value::Null)); + assert!( + obj.get("notification").is_none(), + "rmcp notification must flatten to JSON-RPC method/params" + ); + } + #[tokio::test] async fn test_send_event_as_notification() -> Result<()> { let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded_channel::(); @@ -316,7 +355,7 @@ mod tests { msg: EventMsg::SessionConfigured(session_configured_event.clone()), }; let meta = OutgoingNotificationMeta { - request_id: Some(RequestId::String("123".to_string())), + request_id: Some(RequestId::String("123".into())), thread_id: None, }; @@ -381,7 +420,7 @@ mod tests { msg: EventMsg::SessionConfigured(session_configured_event.clone()), }; let meta = OutgoingNotificationMeta { - request_id: Some(RequestId::String("123".to_string())), + request_id: Some(RequestId::String("123".into())), thread_id: Some(thread_id), }; diff --git a/codex-rs/mcp-server/src/patch_approval.rs b/codex-rs/mcp-server/src/patch_approval.rs index 5c3073959d..55938e257b 100644 --- a/codex-rs/mcp-server/src/patch_approval.rs +++ b/codex-rs/mcp-server/src/patch_approval.rs @@ -7,24 +7,21 @@ use codex_core::protocol::FileChange; use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; use codex_protocol::ThreadId; -use mcp_types::ElicitRequest; -use mcp_types::ElicitRequestParamsRequestedSchema; -use mcp_types::JSONRPCErrorError; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; +use rmcp::model::ErrorData; +use rmcp::model::RequestId; use serde::Deserialize; use serde::Serialize; +use serde_json::Value; use serde_json::json; use tracing::error; -use crate::codex_tool_runner::INVALID_PARAMS_ERROR_CODE; use crate::outgoing_message::OutgoingMessageSender; #[derive(Debug, Deserialize, Serialize)] pub struct PatchApprovalElicitRequestParams { pub message: String, #[serde(rename = "requestedSchema")] - pub requested_schema: ElicitRequestParamsRequestedSchema, + pub requested_schema: Value, #[serde(rename = "threadId")] pub thread_id: ThreadId, pub codex_elicitation: String, @@ -64,11 +61,7 @@ pub(crate) async fn handle_patch_approval_request( let params = PatchApprovalElicitRequestParams { message: message_lines.join("\n"), - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, + requested_schema: json!({"type":"object","properties":{}}), thread_id, codex_elicitation: "patch-approval".to_string(), codex_mcp_tool_call_id: tool_call_id.clone(), @@ -85,14 +78,7 @@ pub(crate) async fn handle_patch_approval_request( error!("{message}"); outgoing - .send_error( - request_id.clone(), - JSONRPCErrorError { - code: INVALID_PARAMS_ERROR_CODE, - message, - data: None, - }, - ) + .send_error(request_id.clone(), ErrorData::invalid_params(message, None)) .await; return; @@ -100,7 +86,7 @@ pub(crate) async fn handle_patch_approval_request( }; let on_response = outgoing - .send_request(ElicitRequest::METHOD, Some(params_json)) + .send_request("elicitation/create", Some(params_json)) .await; // Listen for the response on a separate task so we don't block the main agent loop. @@ -115,7 +101,7 @@ pub(crate) async fn handle_patch_approval_request( pub(crate) async fn on_patch_approval_response( event_id: String, - receiver: tokio::sync::oneshot::Receiver, + receiver: tokio::sync::oneshot::Receiver, codex: Arc, ) { let response = receiver.await; diff --git a/codex-rs/mcp-server/tests/common/Cargo.toml b/codex-rs/mcp-server/tests/common/Cargo.toml index aba984edab..1dec2d09ac 100644 --- a/codex-rs/mcp-server/tests/common/Cargo.toml +++ b/codex-rs/mcp-server/tests/common/Cargo.toml @@ -12,7 +12,7 @@ anyhow = { workspace = true } codex-core = { workspace = true } codex-mcp-server = { workspace = true } codex-utils-cargo-bin = { workspace = true } -mcp-types = { workspace = true } +rmcp = { workspace = true } os_info = { workspace = true } pretty_assertions = { workspace = true } serde = { workspace = true } diff --git a/codex-rs/mcp-server/tests/common/lib.rs b/codex-rs/mcp-server/tests/common/lib.rs index 364c708651..c2c3757efc 100644 --- a/codex-rs/mcp-server/tests/common/lib.rs +++ b/codex-rs/mcp-server/tests/common/lib.rs @@ -6,14 +6,16 @@ pub use core_test_support::format_with_current_shell; pub use core_test_support::format_with_current_shell_display_non_login; pub use core_test_support::format_with_current_shell_non_login; pub use mcp_process::McpProcess; -use mcp_types::JSONRPCResponse; pub use mock_model_server::create_mock_chat_completions_server; pub use responses::create_apply_patch_sse_response; pub use responses::create_final_assistant_message_sse_response; pub use responses::create_shell_command_sse_response; +use rmcp::model::JsonRpcResponse; use serde::de::DeserializeOwned; -pub fn to_response(response: JSONRPCResponse) -> anyhow::Result { +pub fn to_response( + response: JsonRpcResponse, +) -> anyhow::Result { let value = serde_json::to_value(response.result)?; let codex_response = serde_json::from_value(value)?; Ok(codex_response) diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index 9a3f076fb1..c019120282 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -12,19 +12,21 @@ use tokio::process::ChildStdout; use anyhow::Context; use codex_mcp_server::CodexToolCallParam; -use mcp_types::CallToolRequestParams; -use mcp_types::ClientCapabilities; -use mcp_types::Implementation; -use mcp_types::InitializeRequestParams; -use mcp_types::JSONRPC_VERSION; -use mcp_types::JSONRPCMessage; -use mcp_types::JSONRPCNotification; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::ModelContextProtocolNotification; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; use pretty_assertions::assert_eq; +use rmcp::model::CallToolRequestParam; +use rmcp::model::ClientCapabilities; +use rmcp::model::CustomNotification; +use rmcp::model::CustomRequest; +use rmcp::model::ElicitationCapability; +use rmcp::model::Implementation; +use rmcp::model::InitializeRequestParam; +use rmcp::model::JsonRpcMessage; +use rmcp::model::JsonRpcNotification; +use rmcp::model::JsonRpcRequest; +use rmcp::model::JsonRpcResponse; +use rmcp::model::JsonRpcVersion2_0; +use rmcp::model::ProtocolVersion; +use rmcp::model::RequestId; use serde_json::json; use tokio::process::Command; @@ -110,9 +112,11 @@ impl McpProcess { pub async fn initialize(&mut self) -> anyhow::Result<()> { let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed); - let params = InitializeRequestParams { + let params = InitializeRequestParam { capabilities: ClientCapabilities { - elicitation: Some(json!({})), + elicitation: Some(ElicitationCapability { + schema_validation: None, + }), experimental: None, roots: None, sampling: None, @@ -121,17 +125,17 @@ impl McpProcess { name: "elicitation test".into(), title: Some("Elicitation Test".into()), version: "0.0.0".into(), - user_agent: None, + icons: None, + website_url: None, }, - protocol_version: mcp_types::MCP_SCHEMA_VERSION.into(), + protocol_version: ProtocolVersion::V_2025_03_26, }; let params_value = serde_json::to_value(params)?; - self.send_jsonrpc_message(JSONRPCMessage::Request(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(request_id), - method: mcp_types::InitializeRequest::METHOD.into(), - params: Some(params_value), + self.send_jsonrpc_message(JsonRpcMessage::Request(JsonRpcRequest { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(request_id), + request: CustomRequest::new("initialize", Some(params_value)), })) .await?; @@ -146,33 +150,38 @@ impl McpProcess { os_info.architecture().unwrap_or("unknown"), codex_core::terminal::user_agent() ); + let JsonRpcMessage::Response(JsonRpcResponse { + jsonrpc, + id, + result, + }) = initialized + else { + anyhow::bail!("expected initialize response message, got: {initialized:?}") + }; + assert_eq!(jsonrpc, JsonRpcVersion2_0); + assert_eq!(id, RequestId::Number(request_id)); assert_eq!( - JSONRPCMessage::Response(JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(request_id), - result: json!({ - "capabilities": { - "tools": { - "listChanged": true - }, + result, + json!({ + "capabilities": { + "tools": { + "listChanged": true }, - "serverInfo": { - "name": "codex-mcp-server", - "title": "Codex", - "version": "0.0.0", - "user_agent": user_agent - }, - "protocolVersion": mcp_types::MCP_SCHEMA_VERSION - }) - }), - initialized + }, + "serverInfo": { + "name": "codex-mcp-server", + "title": "Codex", + "version": "0.0.0", + "user_agent": user_agent + }, + "protocolVersion": ProtocolVersion::V_2025_03_26 + }) ); // Send notifications/initialized to ack the response. - self.send_jsonrpc_message(JSONRPCMessage::Notification(JSONRPCNotification { - jsonrpc: JSONRPC_VERSION.into(), - method: mcp_types::InitializedNotification::METHOD.into(), - params: None, + self.send_jsonrpc_message(JsonRpcMessage::Notification(JsonRpcNotification { + jsonrpc: JsonRpcVersion2_0, + notification: CustomNotification::new("notifications/initialized", None), })) .await?; @@ -185,12 +194,15 @@ impl McpProcess { &mut self, params: CodexToolCallParam, ) -> anyhow::Result { - let codex_tool_call_params = CallToolRequestParams { - name: "codex".to_string(), - arguments: Some(serde_json::to_value(params)?), + let codex_tool_call_params = CallToolRequestParam { + name: "codex".into(), + arguments: Some(match serde_json::to_value(params)? { + serde_json::Value::Object(map) => map, + _ => unreachable!("params serialize to object"), + }), }; self.send_request( - mcp_types::CallToolRequest::METHOD, + "tools/call", Some(serde_json::to_value(codex_tool_call_params)?), ) .await @@ -203,11 +215,10 @@ impl McpProcess { ) -> anyhow::Result { let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed); - let message = JSONRPCMessage::Request(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(request_id), - method: method.to_string(), - params, + let message = JsonRpcMessage::Request(JsonRpcRequest { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(request_id), + request: CustomRequest::new(method, params), }); self.send_jsonrpc_message(message).await?; Ok(request_id) @@ -218,15 +229,18 @@ impl McpProcess { id: RequestId, result: serde_json::Value, ) -> anyhow::Result<()> { - self.send_jsonrpc_message(JSONRPCMessage::Response(JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), + self.send_jsonrpc_message(JsonRpcMessage::Response(JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, id, result, })) .await } - async fn send_jsonrpc_message(&mut self, message: JSONRPCMessage) -> anyhow::Result<()> { + async fn send_jsonrpc_message( + &mut self, + message: JsonRpcMessage, + ) -> anyhow::Result<()> { eprintln!("writing message to stdin: {message:?}"); let payload = serde_json::to_string(&message)?; self.stdin.write_all(payload.as_bytes()).await?; @@ -235,31 +249,37 @@ impl McpProcess { Ok(()) } - async fn read_jsonrpc_message(&mut self) -> anyhow::Result { + async fn read_jsonrpc_message( + &mut self, + ) -> anyhow::Result> { let mut line = String::new(); self.stdout.read_line(&mut line).await?; - let message = serde_json::from_str::(&line)?; + let message = serde_json::from_str::< + JsonRpcMessage, + >(&line)?; eprintln!("read message from stdout: {message:?}"); Ok(message) } - pub async fn read_stream_until_request_message(&mut self) -> anyhow::Result { + pub async fn read_stream_until_request_message( + &mut self, + ) -> anyhow::Result> { eprintln!("in read_stream_until_request_message()"); loop { let message = self.read_jsonrpc_message().await?; match message { - JSONRPCMessage::Notification(_) => { + JsonRpcMessage::Notification(_) => { eprintln!("notification: {message:?}"); } - JSONRPCMessage::Request(jsonrpc_request) => { + JsonRpcMessage::Request(jsonrpc_request) => { return Ok(jsonrpc_request); } - JSONRPCMessage::Error(_) => { + JsonRpcMessage::Error(_) => { anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}"); } - JSONRPCMessage::Response(_) => { + JsonRpcMessage::Response(_) => { anyhow::bail!("unexpected JSONRPCMessage::Response: {message:?}"); } } @@ -269,22 +289,22 @@ impl McpProcess { pub async fn read_stream_until_response_message( &mut self, request_id: RequestId, - ) -> anyhow::Result { + ) -> anyhow::Result> { eprintln!("in read_stream_until_response_message({request_id:?})"); loop { let message = self.read_jsonrpc_message().await?; match message { - JSONRPCMessage::Notification(_) => { + JsonRpcMessage::Notification(_) => { eprintln!("notification: {message:?}"); } - JSONRPCMessage::Request(_) => { + JsonRpcMessage::Request(_) => { anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}"); } - JSONRPCMessage::Error(_) => { + JsonRpcMessage::Error(_) => { anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}"); } - JSONRPCMessage::Response(jsonrpc_response) => { + JsonRpcMessage::Response(jsonrpc_response) => { if jsonrpc_response.id == request_id { return Ok(jsonrpc_response); } @@ -297,15 +317,15 @@ impl McpProcess { /// Method "codex/event" with params.msg.type == "task_complete". pub async fn read_stream_until_legacy_task_complete_notification( &mut self, - ) -> anyhow::Result { + ) -> anyhow::Result> { eprintln!("in read_stream_until_legacy_task_complete_notification()"); loop { let message = self.read_jsonrpc_message().await?; match message { - JSONRPCMessage::Notification(notification) => { - let is_match = if notification.method == "codex/event" { - if let Some(params) = ¬ification.params { + JsonRpcMessage::Notification(notification) => { + let is_match = if notification.notification.method == "codex/event" { + if let Some(params) = ¬ification.notification.params { params .get("msg") .and_then(|m| m.get("type")) @@ -324,13 +344,13 @@ impl McpProcess { eprintln!("ignoring notification: {notification:?}"); } } - JSONRPCMessage::Request(_) => { + JsonRpcMessage::Request(_) => { anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}"); } - JSONRPCMessage::Error(_) => { + JsonRpcMessage::Error(_) => { anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}"); } - JSONRPCMessage::Response(_) => { + JsonRpcMessage::Response(_) => { anyhow::bail!("unexpected JSONRPCMessage::Response: {message:?}"); } } diff --git a/codex-rs/mcp-server/tests/suite/codex_tool.rs b/codex-rs/mcp-server/tests/suite/codex_tool.rs index 31c451d24f..15836b9057 100644 --- a/codex-rs/mcp-server/tests/suite/codex_tool.rs +++ b/codex-rs/mcp-server/tests/suite/codex_tool.rs @@ -12,14 +12,10 @@ use codex_mcp_server::ExecApprovalElicitRequestParams; use codex_mcp_server::ExecApprovalResponse; use codex_mcp_server::PatchApprovalElicitRequestParams; use codex_mcp_server::PatchApprovalResponse; -use mcp_types::ElicitRequest; -use mcp_types::ElicitRequestParamsRequestedSchema; -use mcp_types::JSONRPC_VERSION; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; use pretty_assertions::assert_eq; +use rmcp::model::JsonRpcResponse; +use rmcp::model::JsonRpcVersion2_0; +use rmcp::model::RequestId; use serde_json::json; use tempfile::TempDir; use tokio::time::timeout; @@ -106,22 +102,27 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> { ) .await??; + assert_eq!(elicitation_request.jsonrpc, JsonRpcVersion2_0); + assert_eq!(elicitation_request.request.method, "elicitation/create"); + let elicitation_request_id = elicitation_request.id.clone(); let params = serde_json::from_value::( elicitation_request + .request .params .clone() .ok_or_else(|| anyhow::anyhow!("elicitation_request.params must be set"))?, )?; - let expected_elicitation_request = create_expected_elicitation_request( - elicitation_request_id.clone(), - expected_shell_command, - workdir_for_shell_function_call.path(), - codex_request_id.to_string(), - params.codex_event_id.clone(), - params.thread_id, - )?; - assert_eq!(expected_elicitation_request, elicitation_request); + assert_eq!( + elicitation_request.request.params, + Some(create_expected_elicitation_request_params( + expected_shell_command, + workdir_for_shell_function_call.path(), + codex_request_id.to_string(), + params.codex_event_id.clone(), + params.thread_id, + )?) + ); // Accept the `git init` request by responding to the elicitation. mcp_process @@ -146,13 +147,13 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> { // Verify the original `codex` tool call completes and that the file was created. let codex_response = timeout( DEFAULT_READ_TIMEOUT, - mcp_process.read_stream_until_response_message(RequestId::Integer(codex_request_id)), + mcp_process.read_stream_until_response_message(RequestId::Number(codex_request_id)), ) .await??; assert_eq!( - JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(codex_request_id), + JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(codex_request_id), result: json!({ "content": [ { @@ -174,41 +175,32 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> { Ok(()) } -fn create_expected_elicitation_request( - elicitation_request_id: RequestId, +fn create_expected_elicitation_request_params( command: Vec, workdir: &Path, codex_mcp_tool_call_id: String, codex_event_id: String, thread_id: codex_protocol::ThreadId, -) -> anyhow::Result { +) -> anyhow::Result { let expected_message = format!( "Allow Codex to run `{}` in `{}`?", shlex::try_join(command.iter().map(std::convert::AsRef::as_ref))?, workdir.to_string_lossy() ); let codex_parsed_cmd = parse_command::parse_command(&command); - Ok(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: elicitation_request_id, - method: ElicitRequest::METHOD.to_string(), - params: Some(serde_json::to_value(&ExecApprovalElicitRequestParams { - message: expected_message, - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, - thread_id, - codex_elicitation: "exec-approval".to_string(), - codex_mcp_tool_call_id, - codex_event_id, - codex_command: command, - codex_cwd: workdir.to_path_buf(), - codex_call_id: "call1234".to_string(), - codex_parsed_cmd, - })?), - }) + let params_json = serde_json::to_value(ExecApprovalElicitRequestParams { + message: expected_message, + requested_schema: json!({"type":"object","properties":{}}), + thread_id, + codex_elicitation: "exec-approval".to_string(), + codex_mcp_tool_call_id, + codex_event_id, + codex_command: command, + codex_cwd: workdir.to_path_buf(), + codex_call_id: "call1234".to_string(), + codex_parsed_cmd, + })?; + Ok(params_json) } /// Test that patch approval triggers an elicitation request to the MCP and that @@ -267,9 +259,13 @@ async fn patch_approval_triggers_elicitation() -> anyhow::Result<()> { ) .await??; + assert_eq!(elicitation_request.jsonrpc, JsonRpcVersion2_0); + assert_eq!(elicitation_request.request.method, "elicitation/create"); + let elicitation_request_id = elicitation_request.id.clone(); let params = serde_json::from_value::( elicitation_request + .request .params .clone() .ok_or_else(|| anyhow::anyhow!("elicitation_request.params must be set"))?, @@ -284,16 +280,17 @@ async fn patch_approval_triggers_elicitation() -> anyhow::Result<()> { }, ); - let expected_elicitation_request = create_expected_patch_approval_elicitation_request( - elicitation_request_id.clone(), - expected_changes, - None, // No grant_root expected - None, // No reason expected - codex_request_id.to_string(), - params.codex_event_id.clone(), - params.thread_id, - )?; - assert_eq!(expected_elicitation_request, elicitation_request); + assert_eq!( + elicitation_request.request.params, + Some(create_expected_patch_approval_elicitation_request_params( + expected_changes, + None, // No grant_root expected + None, // No reason expected + codex_request_id.to_string(), + params.codex_event_id.clone(), + params.thread_id, + )?) + ); // Accept the patch approval request by responding to the elicitation mcp_process @@ -308,13 +305,13 @@ async fn patch_approval_triggers_elicitation() -> anyhow::Result<()> { // Verify the original `codex` tool call completes let codex_response = timeout( DEFAULT_READ_TIMEOUT, - mcp_process.read_stream_until_response_message(RequestId::Integer(codex_request_id)), + mcp_process.read_stream_until_response_message(RequestId::Number(codex_request_id)), ) .await??; assert_eq!( - JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(codex_request_id), + JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(codex_request_id), result: json!({ "content": [ { @@ -375,11 +372,11 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> { let codex_response = timeout( DEFAULT_READ_TIMEOUT, - mcp_process.read_stream_until_response_message(RequestId::Integer(codex_request_id)), + mcp_process.read_stream_until_response_message(RequestId::Number(codex_request_id)), ) .await??; - assert_eq!(codex_response.jsonrpc, JSONRPC_VERSION); - assert_eq!(codex_response.id, RequestId::Integer(codex_request_id)); + assert_eq!(codex_response.jsonrpc, JsonRpcVersion2_0); + assert_eq!(codex_response.id, RequestId::Number(codex_request_id)); assert_eq!( codex_response.result, json!({ @@ -430,42 +427,33 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> { Ok(()) } -fn create_expected_patch_approval_elicitation_request( - elicitation_request_id: RequestId, +fn create_expected_patch_approval_elicitation_request_params( changes: HashMap, grant_root: Option, reason: Option, codex_mcp_tool_call_id: String, codex_event_id: String, thread_id: codex_protocol::ThreadId, -) -> anyhow::Result { +) -> anyhow::Result { let mut message_lines = Vec::new(); if let Some(r) = &reason { message_lines.push(r.clone()); } message_lines.push("Allow Codex to apply proposed code changes?".to_string()); + let params_json = serde_json::to_value(PatchApprovalElicitRequestParams { + message: message_lines.join("\n"), + requested_schema: json!({"type":"object","properties":{}}), + thread_id, + codex_elicitation: "patch-approval".to_string(), + codex_mcp_tool_call_id, + codex_event_id, + codex_reason: reason, + codex_grant_root: grant_root, + codex_changes: changes, + codex_call_id: "call1234".to_string(), + })?; - Ok(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: elicitation_request_id, - method: ElicitRequest::METHOD.to_string(), - params: Some(serde_json::to_value(&PatchApprovalElicitRequestParams { - message: message_lines.join("\n"), - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, - thread_id, - codex_elicitation: "patch-approval".to_string(), - codex_mcp_tool_call_id, - codex_event_id, - codex_reason: reason, - codex_grant_root: grant_root, - codex_changes: changes, - codex_call_id: "call1234".to_string(), - })?), - }) + Ok(params_json) } /// This handle is used to ensure that the MockServer and TempDir are not dropped while diff --git a/codex-rs/protocol/Cargo.toml b/codex-rs/protocol/Cargo.toml index c3d9d30915..b58393936b 100644 --- a/codex-rs/protocol/Cargo.toml +++ b/codex-rs/protocol/Cargo.toml @@ -19,7 +19,6 @@ codex-utils-image = { workspace = true } icu_decimal = { workspace = true } icu_locale_core = { workspace = true } icu_provider = { workspace = true, features = ["sync"] } -mcp-types = { workspace = true } mime_guess = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/codex-rs/protocol/src/approvals.rs b/codex-rs/protocol/src/approvals.rs index 78050dfa86..635cd5223d 100644 --- a/codex-rs/protocol/src/approvals.rs +++ b/codex-rs/protocol/src/approvals.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use std::path::PathBuf; +use crate::mcp::RequestId; use crate::parse_command::ParsedCommand; use crate::protocol::FileChange; -use mcp_types::RequestId; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -62,6 +62,7 @@ pub struct ExecApprovalRequestEvent { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] pub struct ElicitationRequestEvent { pub server_name: String, + #[ts(type = "string | number")] pub id: RequestId, pub message: String, // TODO: MCP servers can request we fill out a schema for the elicitation. We don't support diff --git a/codex-rs/protocol/src/mcp.rs b/codex-rs/protocol/src/mcp.rs index 88a1f989c1..d2a8b0ccd8 100644 --- a/codex-rs/protocol/src/mcp.rs +++ b/codex-rs/protocol/src/mcp.rs @@ -1,14 +1,14 @@ +//! Types used when representing Model Context Protocol (MCP) values inside the +//! Codex protocol. +//! +//! We intentionally keep these types TS/JSON-schema friendly (via `ts-rs` and +//! `schemars`) so they can be embedded in Codex's own protocol structures. use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use ts_rs::TS; -/// Types used when representing Model Context Protocol (MCP) values inside the -/// Codex protocol. -/// -/// We intentionally keep these types TS/JSON-schema friendly (via `ts-rs` and -/// `schemars`) so they can be embedded in Codex's own protocol structures. - +/// ID of a request, which can be either a string or an integer. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)] #[serde(untagged)] pub enum RequestId { @@ -26,6 +26,7 @@ impl std::fmt::Display for RequestId { } } +/// Definition for a tool the client can call. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct Tool { @@ -51,6 +52,7 @@ pub struct Tool { pub meta: Option, } +/// A known resource that the server is capable of reading. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct Resource { @@ -80,6 +82,7 @@ pub struct Resource { pub meta: Option, } +/// A template description for resources available on the server. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct ResourceTemplate { @@ -99,6 +102,7 @@ pub struct ResourceTemplate { pub mime_type: Option, } +/// The server's response to a tool call. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct CallToolResult { diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 9dffb02565..07c8856fe6 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -2,8 +2,6 @@ use std::collections::HashMap; use std::path::Path; use codex_utils_image::load_and_resize_to_fit; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; use serde::Deserialize; use serde::Deserializer; use serde::Serialize; @@ -24,6 +22,8 @@ use codex_git::GhostCommit; use codex_utils_image::error::ImageProcessingError; use schemars::JsonSchema; +use crate::mcp::CallToolResult; + /// Controls whether a command should use the session sandbox or bypass it. #[derive( Debug, Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS, @@ -833,6 +833,7 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { content, structured_content, is_error, + meta: _, } = call_tool_result; let is_success = is_error != &Some(true); @@ -869,7 +870,7 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { } }; - let content_items = convert_content_blocks_to_items(content); + let content_items = convert_mcp_content_to_items(content); FunctionCallOutputPayload { content: serialized_content, @@ -879,32 +880,45 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { } } -fn convert_content_blocks_to_items( - blocks: &[ContentBlock], +fn convert_mcp_content_to_items( + contents: &[serde_json::Value], ) -> Option> { + #[derive(serde::Deserialize)] + #[serde(tag = "type")] + enum McpContent { + #[serde(rename = "text")] + Text { text: String }, + #[serde(rename = "image")] + Image { + data: String, + #[serde(rename = "mimeType", alias = "mime_type")] + mime_type: Option, + }, + #[serde(other)] + Unknown, + } + let mut saw_image = false; - let mut items = Vec::with_capacity(blocks.len()); - tracing::warn!("Blocks: {:?}", blocks); - for block in blocks { - match block { - ContentBlock::TextContent(text) => { - items.push(FunctionCallOutputContentItem::InputText { - text: text.text.clone(), - }); - } - ContentBlock::ImageContent(image) => { + let mut items = Vec::with_capacity(contents.len()); + + for content in contents { + let item = match serde_json::from_value::(content.clone()) { + Ok(McpContent::Text { text }) => FunctionCallOutputContentItem::InputText { text }, + Ok(McpContent::Image { data, mime_type }) => { saw_image = true; - // Just in case the content doesn't include a data URL, add it. - let image_url = if image.data.starts_with("data:") { - image.data.clone() + let image_url = if data.starts_with("data:") { + data } else { - format!("data:{};base64,{}", image.mime_type, image.data) + let mime_type = mime_type.unwrap_or_else(|| "application/octet-stream".into()); + format!("data:{mime_type};base64,{data}") }; - items.push(FunctionCallOutputContentItem::InputImage { image_url }); + FunctionCallOutputContentItem::InputImage { image_url } } - // TODO: render audio, resource, and embedded resource content to the model. - _ => return None, - } + Ok(McpContent::Unknown) | Err(_) => FunctionCallOutputContentItem::InputText { + text: serde_json::to_string(content).unwrap_or_else(|_| "".to_string()), + }, + }; + items.push(item); } if saw_image { Some(items) } else { None } @@ -936,12 +950,54 @@ mod tests { use crate::protocol::AskForApproval; use anyhow::Result; use codex_execpolicy::Policy; - use mcp_types::ImageContent; - use mcp_types::TextContent; use pretty_assertions::assert_eq; use std::path::PathBuf; use tempfile::tempdir; + #[test] + fn convert_mcp_content_to_items_preserves_data_urls() { + let contents = vec![serde_json::json!({ + "type": "image", + "data": "data:image/png;base64,Zm9v", + "mimeType": "image/png", + })]; + + let items = convert_mcp_content_to_items(&contents).expect("expected image items"); + assert_eq!( + items, + vec![FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,Zm9v".to_string(), + }] + ); + } + + #[test] + fn convert_mcp_content_to_items_builds_data_urls_when_missing_prefix() { + let contents = vec![serde_json::json!({ + "type": "image", + "data": "Zm9v", + "mimeType": "image/png", + })]; + + let items = convert_mcp_content_to_items(&contents).expect("expected image items"); + assert_eq!( + items, + vec![FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,Zm9v".to_string(), + }] + ); + } + + #[test] + fn convert_mcp_content_to_items_returns_none_without_images() { + let contents = vec![serde_json::json!({ + "type": "text", + "text": "hello", + })]; + + assert_eq!(convert_mcp_content_to_items(&contents), None); + } + #[test] fn converts_sandbox_mode_into_developer_instructions() { let workspace_write: DeveloperInstructions = SandboxMode::WorkspaceWrite.into(); @@ -1124,20 +1180,12 @@ mod tests { fn serializes_image_outputs_as_array() -> Result<()> { let call_tool_result = CallToolResult { content: vec![ - ContentBlock::TextContent(TextContent { - annotations: None, - text: "caption".into(), - r#type: "text".into(), - }), - ContentBlock::ImageContent(ImageContent { - annotations: None, - data: "BASE64".into(), - mime_type: "image/png".into(), - r#type: "image".into(), - }), + serde_json::json!({"type":"text","text":"caption"}), + serde_json::json!({"type":"image","data":"BASE64","mimeType":"image/png"}), ], - is_error: None, structured_content: None, + is_error: Some(false), + meta: None, }; let payload = FunctionCallOutputPayload::from(&call_tool_result); @@ -1169,6 +1217,33 @@ mod tests { Ok(()) } + #[test] + fn preserves_existing_image_data_urls() -> Result<()> { + let call_tool_result = CallToolResult { + content: vec![serde_json::json!({ + "type": "image", + "data": "data:image/png;base64,BASE64", + "mimeType": "image/png" + })], + structured_content: None, + is_error: Some(false), + meta: None, + }; + + let payload = FunctionCallOutputPayload::from(&call_tool_result); + let Some(items) = payload.content_items else { + panic!("expected content items"); + }; + assert_eq!( + items, + vec![FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,BASE64".into(), + }] + ); + + Ok(()) + } + #[test] fn deserializes_array_payload_into_items() -> Result<()> { let json = r#"[ diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 6a6939620f..71a7973c6d 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -23,6 +23,11 @@ use crate::dynamic_tools::DynamicToolCallRequest; use crate::dynamic_tools::DynamicToolResponse; use crate::dynamic_tools::DynamicToolSpec; use crate::items::TurnItem; +use crate::mcp::CallToolResult; +use crate::mcp::RequestId; +use crate::mcp::Resource as McpResource; +use crate::mcp::ResourceTemplate as McpResourceTemplate; +use crate::mcp::Tool as McpTool; use crate::message_history::HistoryEntry; use crate::models::BaseInstructions; use crate::models::ContentItem; @@ -35,11 +40,6 @@ use crate::plan_tool::UpdatePlanArgs; use crate::request_user_input::RequestUserInputResponse; use crate::user_input::UserInput; use codex_utils_absolute_path::AbsolutePathBuf; -use mcp_types::CallToolResult; -use mcp_types::RequestId; -use mcp_types::Resource as McpResource; -use mcp_types::ResourceTemplate as McpResourceTemplate; -use mcp_types::Tool as McpTool; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; diff --git a/codex-rs/rmcp-client/Cargo.toml b/codex-rs/rmcp-client/Cargo.toml index 4b2d6964f4..2b8752fcea 100644 --- a/codex-rs/rmcp-client/Cargo.toml +++ b/codex-rs/rmcp-client/Cargo.toml @@ -18,7 +18,6 @@ codex-protocol = { workspace = true } codex-utils-home-dir = { workspace = true } futures = { workspace = true, default-features = false, features = ["std"] } keyring = { workspace = true, features = ["crypto-rust"] } -mcp-types = { path = "../mcp-types" } oauth2 = "5" reqwest = { version = "0.12", default-features = false, features = [ "json", diff --git a/codex-rs/rmcp-client/src/logging_client_handler.rs b/codex-rs/rmcp-client/src/logging_client_handler.rs index 0d2c3aaa97..8db730df06 100644 --- a/codex-rs/rmcp-client/src/logging_client_handler.rs +++ b/codex-rs/rmcp-client/src/logging_client_handler.rs @@ -9,7 +9,6 @@ use rmcp::model::CreateElicitationResult; use rmcp::model::LoggingLevel; use rmcp::model::LoggingMessageNotificationParam; use rmcp::model::ProgressNotificationParam; -use rmcp::model::RequestId; use rmcp::model::ResourceUpdatedNotificationParam; use rmcp::service::NotificationContext; use rmcp::service::RequestContext; @@ -41,11 +40,7 @@ impl ClientHandler for LoggingClientHandler { request: CreateElicitationRequestParam, context: RequestContext, ) -> Result { - let id = match context.id { - RequestId::String(id) => mcp_types::RequestId::String(id.to_string()), - RequestId::Number(id) => mcp_types::RequestId::Integer(id), - }; - (self.send_elicitation)(id, request) + (self.send_elicitation)(context.id, request) .await .map_err(|err| rmcp::ErrorData::internal_error(err.to_string(), None)) } diff --git a/codex-rs/rmcp-client/src/rmcp_client.rs b/codex-rs/rmcp-client/src/rmcp_client.rs index c1bf6d39d3..12b4b52b91 100644 --- a/codex-rs/rmcp-client/src/rmcp_client.rs +++ b/codex-rs/rmcp-client/src/rmcp_client.rs @@ -10,22 +10,9 @@ use anyhow::Result; use anyhow::anyhow; use futures::FutureExt; use futures::future::BoxFuture; -use mcp_types::CallToolRequestParams; -use mcp_types::CallToolResult; -use mcp_types::InitializeRequestParams; -use mcp_types::InitializeResult; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ListToolsRequestParams; -use mcp_types::ListToolsResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::RequestId; -use mcp_types::Tool; use reqwest::header::HeaderMap; use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolResult; use rmcp::model::ClientNotification; use rmcp::model::ClientRequest; use rmcp::model::CreateElicitationRequestParam; @@ -34,9 +21,16 @@ use rmcp::model::CustomNotification; use rmcp::model::CustomRequest; use rmcp::model::Extensions; use rmcp::model::InitializeRequestParam; +use rmcp::model::InitializeResult; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::ListToolsResult; use rmcp::model::PaginatedRequestParam; use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::RequestId; use rmcp::model::ServerResult; +use rmcp::model::Tool; use rmcp::service::RoleClient; use rmcp::service::RunningService; use rmcp::service::{self}; @@ -62,9 +56,6 @@ use crate::oauth::StoredOAuthTokens; use crate::program_resolver; use crate::utils::apply_default_headers; use crate::utils::build_default_headers; -use crate::utils::convert_call_tool_result; -use crate::utils::convert_to_mcp; -use crate::utils::convert_to_rmcp; use crate::utils::create_env_for_mcp_server; use crate::utils::run_with_timeout; @@ -229,12 +220,11 @@ impl RmcpClient { /// https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization pub async fn initialize( &self, - params: InitializeRequestParams, + params: InitializeRequestParam, timeout: Option, send_elicitation: SendElicitation, ) -> Result { - let rmcp_params: InitializeRequestParam = convert_to_rmcp(params.clone())?; - let client_handler = LoggingClientHandler::new(rmcp_params, send_elicitation); + let client_handler = LoggingClientHandler::new(params.clone(), send_elicitation); let (transport, oauth_persistor) = { let mut guard = self.state.lock().await; @@ -275,7 +265,7 @@ impl RmcpClient { .peer() .peer_info() .ok_or_else(|| anyhow!("handshake succeeded but server info was missing"))?; - let initialize_result = convert_to_mcp(initialize_result_rmcp)?; + let initialize_result = initialize_result_rmcp.clone(); { let mut guard = self.state.lock().await; @@ -296,28 +286,26 @@ impl RmcpClient { pub async fn list_tools( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { - let result = self.list_tools_with_connector_ids(params, timeout).await?; - Ok(ListToolsResult { - next_cursor: result.next_cursor, - tools: result.tools.into_iter().map(|tool| tool.tool).collect(), - }) + self.refresh_oauth_if_needed().await; + let service = self.service().await?; + let fut = service.list_tools(params); + let result = run_with_timeout(fut, timeout, "tools/list").await?; + self.persist_oauth_tokens().await; + Ok(result) } pub async fn list_tools_with_connector_ids( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params = params - .map(convert_to_rmcp::<_, PaginatedRequestParam>) - .transpose()?; - let fut = service.list_tools(rmcp_params); + let fut = service.list_tools(params); let result = run_with_timeout(fut, timeout, "tools/list").await?; let tools = result .tools @@ -327,7 +315,6 @@ impl RmcpClient { let connector_id = Self::meta_string(meta, "connector_id"); let connector_name = Self::meta_string(meta, "connector_name") .or_else(|| Self::meta_string(meta, "connector_display_name")); - let tool = convert_to_mcp(tool)?; Ok(ToolWithConnectorId { tool, connector_id, @@ -352,53 +339,43 @@ impl RmcpClient { pub async fn list_resources( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params = params - .map(convert_to_rmcp::<_, PaginatedRequestParam>) - .transpose()?; - let fut = service.list_resources(rmcp_params); + let fut = service.list_resources(params); let result = run_with_timeout(fut, timeout, "resources/list").await?; - let converted = convert_to_mcp(result)?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn list_resource_templates( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params = params - .map(convert_to_rmcp::<_, PaginatedRequestParam>) - .transpose()?; - let fut = service.list_resource_templates(rmcp_params); + let fut = service.list_resource_templates(params); let result = run_with_timeout(fut, timeout, "resources/templates/list").await?; - let converted = convert_to_mcp(result)?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn read_resource( &self, - params: ReadResourceRequestParams, + params: ReadResourceRequestParam, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params: ReadResourceRequestParam = convert_to_rmcp(params)?; - let fut = service.read_resource(rmcp_params); + let fut = service.read_resource(params); let result = run_with_timeout(fut, timeout, "resources/read").await?; - let converted = convert_to_mcp(result)?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn call_tool( @@ -409,13 +386,23 @@ impl RmcpClient { ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let params = CallToolRequestParams { arguments, name }; - let rmcp_params: CallToolRequestParam = convert_to_rmcp(params)?; + let arguments = match arguments { + Some(Value::Object(map)) => Some(map), + Some(other) => { + return Err(anyhow!( + "MCP tool arguments must be a JSON object, got {other}" + )); + } + None => None, + }; + let rmcp_params = CallToolRequestParam { + name: name.into(), + arguments, + }; let fut = service.call_tool(rmcp_params); - let rmcp_result = run_with_timeout(fut, timeout, "tools/call").await?; - let converted = convert_call_tool_result(rmcp_result)?; + let result = run_with_timeout(fut, timeout, "tools/call").await?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn send_custom_notification( diff --git a/codex-rs/rmcp-client/src/utils.rs b/codex-rs/rmcp-client/src/utils.rs index 8deb4d402b..e47c1d14b6 100644 --- a/codex-rs/rmcp-client/src/utils.rs +++ b/codex-rs/rmcp-client/src/utils.rs @@ -5,14 +5,11 @@ use std::time::Duration; use anyhow::Context; use anyhow::Result; use anyhow::anyhow; -use mcp_types::CallToolResult; use reqwest::ClientBuilder; use reqwest::header::HeaderMap; use reqwest::header::HeaderName; use reqwest::header::HeaderValue; -use rmcp::model::CallToolResult as RmcpCallToolResult; use rmcp::service::ServiceError; -use serde_json::Value; use tokio::time; pub(crate) async fn run_with_timeout( @@ -33,45 +30,6 @@ where } } -pub(crate) fn convert_call_tool_result(result: RmcpCallToolResult) -> Result { - let mut value = serde_json::to_value(result)?; - if let Some(obj) = value.as_object_mut() - && (obj.get("content").is_none() - || obj.get("content").is_some_and(serde_json::Value::is_null)) - { - obj.insert("content".to_string(), Value::Array(Vec::new())); - } - serde_json::from_value(value).context("failed to convert call tool result") -} - -/// Convert from mcp-types to Rust SDK types. -/// -/// The Rust SDK types are the same as our mcp-types crate because they are both -/// derived from the same MCP specification. -/// As a result, it should be safe to convert directly from one to the other. -pub(crate) fn convert_to_rmcp(value: T) -> Result -where - T: serde::Serialize, - U: serde::de::DeserializeOwned, -{ - let json = serde_json::to_value(value)?; - serde_json::from_value(json).map_err(|err| anyhow!(err)) -} - -/// Convert from Rust SDK types to mcp-types. -/// -/// The Rust SDK types are the same as our mcp-types crate because they are both -/// derived from the same MCP specification. -/// As a result, it should be safe to convert directly from one to the other. -pub(crate) fn convert_to_mcp(value: T) -> Result -where - T: serde::Serialize, - U: serde::de::DeserializeOwned, -{ - let json = serde_json::to_value(value)?; - serde_json::from_value(json).map_err(|err| anyhow!(err)) -} - pub(crate) fn create_env_for_mcp_server( extra_env: Option>, env_vars: &[String], @@ -203,10 +161,7 @@ pub(crate) const DEFAULT_ENV_VARS: &[&str] = &[ #[cfg(test)] mod tests { use super::*; - use mcp_types::ContentBlock; use pretty_assertions::assert_eq; - use rmcp::model::CallToolResult as RmcpCallToolResult; - use serde_json::json; use serial_test::serial; use std::ffi::OsString; @@ -260,43 +215,4 @@ mod tests { let env = create_env_for_mcp_server(None, &[custom_var.to_string()]); assert_eq!(env.get(custom_var), Some(&value.to_string())); } - - #[test] - fn convert_call_tool_result_defaults_missing_content() -> Result<()> { - let structured_content = json!({ "key": "value" }); - let rmcp_result = RmcpCallToolResult { - content: vec![], - structured_content: Some(structured_content.clone()), - is_error: Some(true), - meta: None, - }; - - let result = convert_call_tool_result(rmcp_result)?; - - assert!(result.content.is_empty()); - assert_eq!(result.structured_content, Some(structured_content)); - assert_eq!(result.is_error, Some(true)); - - Ok(()) - } - - #[test] - fn convert_call_tool_result_preserves_existing_content() -> Result<()> { - let rmcp_result = RmcpCallToolResult::success(vec![rmcp::model::Content::text("hello")]); - - let result = convert_call_tool_result(rmcp_result)?; - - assert_eq!(result.content.len(), 1); - match &result.content[0] { - ContentBlock::TextContent(text_content) => { - assert_eq!(text_content.text, "hello"); - assert_eq!(text_content.r#type, "text"); - } - other => panic!("expected text content got {other:?}"), - } - assert_eq!(result.structured_content, None); - assert_eq!(result.is_error, Some(false)); - - Ok(()) - } } diff --git a/codex-rs/rmcp-client/tests/resources.rs b/codex-rs/rmcp-client/tests/resources.rs index 3d627ebbf4..9bfd77c183 100644 --- a/codex-rs/rmcp-client/tests/resources.rs +++ b/codex-rs/rmcp-client/tests/resources.rs @@ -7,15 +7,15 @@ use codex_rmcp_client::ElicitationResponse; use codex_rmcp_client::RmcpClient; use codex_utils_cargo_bin::CargoBinError; use futures::FutureExt as _; -use mcp_types::ClientCapabilities; -use mcp_types::Implementation; -use mcp_types::InitializeRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResultContents; -use mcp_types::Resource; -use mcp_types::ResourceTemplate; -use mcp_types::TextResourceContents; +use rmcp::model::AnnotateAble; +use rmcp::model::ClientCapabilities; +use rmcp::model::ElicitationCapability; +use rmcp::model::Implementation; +use rmcp::model::InitializeRequestParam; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ProtocolVersion; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ResourceContents; use serde_json::json; const RESOURCE_URI: &str = "memo://codex/example-note"; @@ -24,21 +24,24 @@ fn stdio_server_bin() -> Result { codex_utils_cargo_bin::cargo_bin("test_stdio_server") } -fn init_params() -> InitializeRequestParams { - InitializeRequestParams { +fn init_params() -> InitializeRequestParam { + InitializeRequestParam { capabilities: ClientCapabilities { experimental: None, roots: None, sampling: None, - elicitation: Some(json!({})), + elicitation: Some(ElicitationCapability { + schema_validation: None, + }), }, client_info: Implementation { name: "codex-test".into(), version: "0.0.0-test".into(), title: Some("Codex rmcp resource test".into()), - user_agent: None, + icons: None, + website_url: None, }, - protocol_version: mcp_types::MCP_SCHEMA_VERSION.to_string(), + protocol_version: ProtocolVersion::V_2025_06_18, } } @@ -79,15 +82,17 @@ async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()> { .expect("memo resource present"); assert_eq!( memo, - &Resource { - annotations: None, + &rmcp::model::RawResource { + uri: RESOURCE_URI.to_string(), + name: "example-note".to_string(), + title: Some("Example Note".to_string()), description: Some("A sample MCP resource exposed for integration tests.".to_string()), mime_type: Some("text/plain".to_string()), - name: "example-note".to_string(), size: None, - title: Some("Example Note".to_string()), - uri: RESOURCE_URI.to_string(), + icons: None, + meta: None, } + .no_annotation() ); let templates = client .list_resource_templates(None, Some(Duration::from_secs(5))) @@ -95,39 +100,39 @@ async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()> { assert_eq!( templates, ListResourceTemplatesResult { + meta: None, next_cursor: None, - resource_templates: vec![ResourceTemplate { - annotations: None, - description: Some( - "Template for memo://codex/{slug} resources used in tests.".to_string() - ), - mime_type: Some("text/plain".to_string()), - name: "codex-memo".to_string(), - title: Some("Codex Memo".to_string()), - uri_template: "memo://codex/{slug}".to_string(), - }], + resource_templates: vec![ + rmcp::model::RawResourceTemplate { + uri_template: "memo://codex/{slug}".to_string(), + name: "codex-memo".to_string(), + title: Some("Codex Memo".to_string()), + description: Some( + "Template for memo://codex/{slug} resources used in tests.".to_string(), + ), + mime_type: Some("text/plain".to_string()), + } + .no_annotation() + ], } ); let read = client .read_resource( - ReadResourceRequestParams { + ReadResourceRequestParam { uri: RESOURCE_URI.to_string(), }, Some(Duration::from_secs(5)), ) .await?; - let ReadResourceResultContents::TextResourceContents(text) = - read.contents.first().expect("resource contents present") - else { - panic!("expected text resource"); - }; + let text = read.contents.first().expect("resource contents present"); assert_eq!( text, - &TextResourceContents { - text: "This is a sample MCP resource served by the rmcp test server.".to_string(), + &ResourceContents::TextResourceContents { uri: RESOURCE_URI.to_string(), mime_type: Some("text/plain".to_string()), + text: "This is a sample MCP resource served by the rmcp test server.".to_string(), + meta: None, } ); diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index d859aa945e..8bdd473220 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -54,7 +54,6 @@ dunce = { workspace = true } image = { workspace = true, features = ["jpeg", "png"] } itertools = { workspace = true } lazy_static = { workspace = true } -mcp-types = { workspace = true } pathdiff = { workspace = true } pulldown-cmark = { workspace = true } rand = { workspace = true } @@ -67,6 +66,7 @@ ratatui = { workspace = true, features = [ ratatui-macros = { workspace = true } regex-lite = { workspace = true } reqwest = { version = "0.12", features = ["json"] } +rmcp = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["preserve_order"] } shlex = { workspace = true } diff --git a/codex-rs/tui/src/bottom_pane/approval_overlay.rs b/codex-rs/tui/src/bottom_pane/approval_overlay.rs index 0f0445fee8..15f252e86f 100644 --- a/codex-rs/tui/src/bottom_pane/approval_overlay.rs +++ b/codex-rs/tui/src/bottom_pane/approval_overlay.rs @@ -23,11 +23,11 @@ use codex_core::protocol::ExecPolicyAmendment; use codex_core::protocol::FileChange; use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; +use codex_protocol::mcp::RequestId; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyEventKind; use crossterm::event::KeyModifiers; -use mcp_types::RequestId; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Stylize; diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap index 77738439a1..38fb05e28d 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap @@ -27,7 +27,7 @@ expression: "lines[start_idx..].join(\"\\n\")" exec, linux-sandbox, tui, login, ollama, and mcp. • Ran for d in ansi-escape apply-patch arg0 cli common core exec execpolicy - │ file-search linux-sandbox login mcp-client mcp-server mcp-types ollama + │ file-search linux-sandbox login mcp-client mcp-server ollama │ tui; do echo "--- $d/Cargo.toml"; sed -n '1,200p' $d/Cargo.toml; echo; │ … +1 lines └ --- ansi-escape/Cargo.toml diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 0258e05968..a91b62a754 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -47,6 +47,8 @@ use codex_core::protocol::SessionConfiguredEvent; use codex_core::web_search::web_search_detail; use codex_otel::RuntimeMetricsSummary; use codex_protocol::account::PlanType; +use codex_protocol::mcp::Resource; +use codex_protocol::mcp::ResourceTemplate; use codex_protocol::models::WebSearchAction; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::PlanItemArg; @@ -57,10 +59,6 @@ use codex_protocol::request_user_input::RequestUserInputQuestion; use codex_protocol::user_input::TextElement; use image::DynamicImage; use image::ImageReader; -use mcp_types::EmbeddedResourceResource; -use mcp_types::Resource; -use mcp_types::ResourceLink; -use mcp_types::ResourceTemplate; use ratatui::prelude::*; use ratatui::style::Color; use ratatui::style::Modifier; @@ -1203,7 +1201,7 @@ pub(crate) struct McpToolCallCell { invocation: McpInvocation, start_time: Instant, duration: Option, - result: Option>, + result: Option>, animations_enabled: bool, } @@ -1230,7 +1228,7 @@ impl McpToolCallCell { pub(crate) fn complete( &mut self, duration: Duration, - result: Result, + result: Result, ) -> Option> { let image_cell = try_new_completed_mcp_tool_call_with_image_output(&result) .map(|cell| Box::new(cell) as Box); @@ -1253,23 +1251,32 @@ impl McpToolCallCell { self.result = Some(Err("interrupted".to_string())); } - fn render_content_block(block: &mcp_types::ContentBlock, width: usize) -> String { - match block { - mcp_types::ContentBlock::TextContent(text) => { + fn render_content_block(block: &serde_json::Value, width: usize) -> String { + let content = match serde_json::from_value::(block.clone()) { + Ok(content) => content, + Err(_) => { + return format_and_truncate_tool_result( + &block.to_string(), + TOOL_CALL_MAX_LINES, + width, + ); + } + }; + + match content.raw { + rmcp::model::RawContent::Text(text) => { format_and_truncate_tool_result(&text.text, TOOL_CALL_MAX_LINES, width) } - mcp_types::ContentBlock::ImageContent(_) => "".to_string(), - mcp_types::ContentBlock::AudioContent(_) => " { let mut headers = self.provider.headers.clone(); headers.extend(extra_headers); - apply_auth_headers(&mut headers, &self.auth); + add_auth_headers_to_header_map(&self.auth, &mut headers); let (stream, server_reasoning_included) = connect_websocket(ws_url, headers, turn_state).await?; @@ -147,20 +147,6 @@ impl ResponsesWebsocketClient { } } -// TODO (pakrym): share with /auth -fn apply_auth_headers(headers: &mut HeaderMap, auth: &impl AuthProvider) { - if let Some(token) = auth.bearer_token() - && let Ok(header) = HeaderValue::from_str(&format!("Bearer {token}")) - { - let _ = headers.insert(http::header::AUTHORIZATION, header); - } - if let Some(account_id) = auth.account_id() - && let Ok(header) = HeaderValue::from_str(&account_id) - { - let _ = headers.insert("ChatGPT-Account-ID", header); - } -} - async fn connect_websocket( url: Url, headers: HeaderMap, diff --git a/codex-rs/codex-api/src/endpoint/session.rs b/codex-rs/codex-api/src/endpoint/session.rs new file mode 100644 index 0000000000..a6cd7bfe37 --- /dev/null +++ b/codex-rs/codex-api/src/endpoint/session.rs @@ -0,0 +1,126 @@ +use crate::auth::AuthProvider; +use crate::auth::add_auth_headers; +use crate::error::ApiError; +use crate::provider::Provider; +use crate::telemetry::run_with_request_telemetry; +use codex_client::HttpTransport; +use codex_client::Request; +use codex_client::RequestTelemetry; +use codex_client::Response; +use codex_client::StreamResponse; +use http::HeaderMap; +use http::Method; +use serde_json::Value; +use std::sync::Arc; + +pub(crate) struct EndpointSession { + transport: T, + provider: Provider, + auth: A, + request_telemetry: Option>, +} + +impl EndpointSession { + pub(crate) fn new(transport: T, provider: Provider, auth: A) -> Self { + Self { + transport, + provider, + auth, + request_telemetry: None, + } + } + + pub(crate) fn with_request_telemetry( + mut self, + request: Option>, + ) -> Self { + self.request_telemetry = request; + self + } + + pub(crate) fn provider(&self) -> &Provider { + &self.provider + } + + fn make_request( + &self, + method: &Method, + path: &str, + extra_headers: &HeaderMap, + body: Option<&Value>, + ) -> Request { + let mut req = self.provider.build_request(method.clone(), path); + req.headers.extend(extra_headers.clone()); + if let Some(body) = body { + req.body = Some(body.clone()); + } + add_auth_headers(&self.auth, req) + } + + pub(crate) async fn execute( + &self, + method: Method, + path: &str, + extra_headers: HeaderMap, + body: Option, + ) -> Result { + self.execute_with(method, path, extra_headers, body, |_| {}) + .await + } + + pub(crate) async fn execute_with( + &self, + method: Method, + path: &str, + extra_headers: HeaderMap, + body: Option, + configure: C, + ) -> Result + where + C: Fn(&mut Request), + { + let make_request = || { + let mut req = self.make_request(&method, path, &extra_headers, body.as_ref()); + configure(&mut req); + req + }; + + let response = run_with_request_telemetry( + self.provider.retry.to_policy(), + self.request_telemetry.clone(), + make_request, + |req| self.transport.execute(req), + ) + .await?; + + Ok(response) + } + + pub(crate) async fn stream_with( + &self, + method: Method, + path: &str, + extra_headers: HeaderMap, + body: Option, + configure: C, + ) -> Result + where + C: Fn(&mut Request), + { + let make_request = || { + let mut req = self.make_request(&method, path, &extra_headers, body.as_ref()); + configure(&mut req); + req + }; + + let stream = run_with_request_telemetry( + self.provider.retry.to_policy(), + self.request_telemetry.clone(), + make_request, + |req| self.transport.stream(req), + ) + .await?; + + Ok(stream) + } +} diff --git a/codex-rs/codex-api/src/endpoint/streaming.rs b/codex-rs/codex-api/src/endpoint/streaming.rs deleted file mode 100644 index 15d4c077a0..0000000000 --- a/codex-rs/codex-api/src/endpoint/streaming.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::auth::AuthProvider; -use crate::auth::add_auth_headers; -use crate::common::ResponseStream; -use crate::error::ApiError; -use crate::provider::Provider; -use crate::telemetry::SseTelemetry; -use crate::telemetry::run_with_request_telemetry; -use codex_client::HttpTransport; -use codex_client::RequestCompression; -use codex_client::RequestTelemetry; -use codex_client::StreamResponse; -use http::HeaderMap; -use http::Method; -use serde_json::Value; -use std::sync::Arc; -use std::sync::OnceLock; -use std::time::Duration; - -pub(crate) struct StreamingClient { - transport: T, - provider: Provider, - auth: A, - request_telemetry: Option>, - sse_telemetry: Option>, -} - -type StreamSpawner = fn( - StreamResponse, - Duration, - Option>, - Option>>, -) -> ResponseStream; - -impl StreamingClient { - pub(crate) fn new(transport: T, provider: Provider, auth: A) -> Self { - Self { - transport, - provider, - auth, - request_telemetry: None, - sse_telemetry: None, - } - } - - pub(crate) fn with_telemetry( - mut self, - request: Option>, - sse: Option>, - ) -> Self { - self.request_telemetry = request; - self.sse_telemetry = sse; - self - } - - pub(crate) fn provider(&self) -> &Provider { - &self.provider - } - - pub(crate) async fn stream( - &self, - path: &str, - body: Value, - extra_headers: HeaderMap, - compression: RequestCompression, - spawner: StreamSpawner, - turn_state: Option>>, - ) -> Result { - let builder = || { - let mut req = self.provider.build_request(Method::POST, path); - req.headers.extend(extra_headers.clone()); - req.headers.insert( - http::header::ACCEPT, - http::HeaderValue::from_static("text/event-stream"), - ); - req.body = Some(body.clone()); - req.compression = compression; - add_auth_headers(&self.auth, req) - }; - - let stream_response = run_with_request_telemetry( - self.provider.retry.to_policy(), - self.request_telemetry.clone(), - builder, - |req| self.transport.stream(req), - ) - .await?; - - Ok(spawner( - stream_response, - self.provider.stream_idle_timeout, - self.sse_telemetry.clone(), - turn_state, - )) - } -} From 944541e93640a744f1a7aed2f214d2fa567f5cad Mon Sep 17 00:00:00 2001 From: gt-oai Date: Tue, 3 Feb 2026 14:58:33 +0000 Subject: [PATCH 104/139] Add more detail to 401 error (#10508) Add the error.message if it exists, the body otherwise. Truncate body to 1k characters. Print the cf-ray and the requestId. **Before:** Screenshot 2026-02-03 at 13 15 28 **After:** Screenshot 2026-02-03 at 13 15 38 --- codex-rs/core/src/api_bridge.rs | 26 ++++--- codex-rs/core/src/client.rs | 29 +++----- codex-rs/core/src/error.rs | 117 ++++++++++++++++++++++++++++++-- 3 files changed, 140 insertions(+), 32 deletions(-) diff --git a/codex-rs/core/src/api_bridge.rs b/codex-rs/core/src/api_bridge.rs index 86aeaedda0..f7aeb570bd 100644 --- a/codex-rs/core/src/api_bridge.rs +++ b/codex-rs/core/src/api_bridge.rs @@ -28,6 +28,7 @@ pub(crate) fn map_api_error(err: ApiError) -> CodexErr { status, body: message, url: None, + cf_ray: None, request_id: None, }), ApiError::InvalidRequest { message } => CodexErr::InvalidRequest(message), @@ -89,13 +90,14 @@ pub(crate) fn map_api_error(err: ApiError) -> CodexErr { CodexErr::RetryLimit(RetryLimitReachedError { status, - request_id: extract_request_id(headers.as_ref()), + request_id: extract_request_tracking_id(headers.as_ref()), }) } else { CodexErr::UnexpectedStatus(UnexpectedResponseError { status, body: body_text, url, + cf_ray: extract_header(headers.as_ref(), CF_RAY_HEADER), request_id: extract_request_id(headers.as_ref()), }) } @@ -115,6 +117,9 @@ pub(crate) fn map_api_error(err: ApiError) -> CodexErr { const MODEL_CAP_MODEL_HEADER: &str = "x-codex-model-cap-model"; const MODEL_CAP_RESET_AFTER_HEADER: &str = "x-codex-model-cap-reset-after-seconds"; +const REQUEST_ID_HEADER: &str = "x-request-id"; +const OAI_REQUEST_ID_HEADER: &str = "x-oai-request-id"; +const CF_RAY_HEADER: &str = "cf-ray"; #[cfg(test)] mod tests { @@ -149,15 +154,20 @@ mod tests { } } +fn extract_request_tracking_id(headers: Option<&HeaderMap>) -> Option { + extract_request_id(headers).or_else(|| extract_header(headers, CF_RAY_HEADER)) +} + fn extract_request_id(headers: Option<&HeaderMap>) -> Option { + extract_header(headers, REQUEST_ID_HEADER) + .or_else(|| extract_header(headers, OAI_REQUEST_ID_HEADER)) +} + +fn extract_header(headers: Option<&HeaderMap>, name: &str) -> Option { headers.and_then(|map| { - ["cf-ray", "x-request-id", "x-oai-request-id"] - .iter() - .find_map(|name| { - map.get(*name) - .and_then(|v| v.to_str().ok()) - .map(str::to_string) - }) + map.get(name) + .and_then(|value| value.to_str().ok()) + .map(str::to_string) }) } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 953b11b8f8..fcc308f380 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -567,10 +567,10 @@ impl ModelClientSession { Ok(stream) => { return Ok(map_response_stream(stream, self.state.otel_manager.clone())); } - Err(ApiError::Transport(TransportError::Http { status, .. })) - if status == StatusCode::UNAUTHORIZED => - { - handle_unauthorized(status, &mut auth_recovery).await?; + Err(ApiError::Transport( + unauthorized_transport @ TransportError::Http { status, .. }, + )) if status == StatusCode::UNAUTHORIZED => { + handle_unauthorized(unauthorized_transport, &mut auth_recovery).await?; continue; } Err(err) => return Err(map_api_error(err)), @@ -606,10 +606,10 @@ impl ModelClientSession { .await { Ok(connection) => connection, - Err(ApiError::Transport(TransportError::Http { status, .. })) - if status == StatusCode::UNAUTHORIZED => - { - handle_unauthorized(status, &mut auth_recovery).await?; + Err(ApiError::Transport( + unauthorized_transport @ TransportError::Http { status, .. }, + )) if status == StatusCode::UNAUTHORIZED => { + handle_unauthorized(unauthorized_transport, &mut auth_recovery).await?; continue; } Err(err) => return Err(map_api_error(err)), @@ -780,7 +780,7 @@ where /// When refresh succeeds, the caller should retry the API call; otherwise /// the mapped `CodexErr` is returned to the caller. async fn handle_unauthorized( - status: StatusCode, + transport: TransportError, auth_recovery: &mut Option, ) -> Result<()> { if let Some(recovery) = auth_recovery @@ -793,16 +793,7 @@ async fn handle_unauthorized( }; } - Err(map_unauthorized_status(status)) -} - -fn map_unauthorized_status(status: StatusCode) -> CodexErr { - map_api_error(ApiError::Transport(TransportError::Http { - status, - url: None, - headers: None, - body: None, - })) + Err(map_api_error(ApiError::Transport(transport))) } struct ApiTelemetry { diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index 684d968162..889335824e 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -286,13 +286,42 @@ pub struct UnexpectedResponseError { pub status: StatusCode, pub body: String, pub url: Option, + pub cf_ray: Option, pub request_id: Option, } const CLOUDFLARE_BLOCKED_MESSAGE: &str = "Access blocked by Cloudflare. This usually happens when connecting from a restricted region"; +const UNEXPECTED_RESPONSE_BODY_MAX_BYTES: usize = 1000; impl UnexpectedResponseError { + fn display_body(&self) -> String { + if let Some(message) = self.extract_error_message() { + return message; + } + + let trimmed_body = self.body.trim(); + if trimmed_body.is_empty() { + return "Unknown error".to_string(); + } + + truncate_with_ellipsis(trimmed_body, UNEXPECTED_RESPONSE_BODY_MAX_BYTES) + } + + fn extract_error_message(&self) -> Option { + let json = serde_json::from_str::(&self.body).ok()?; + let message = json + .get("error") + .and_then(|error| error.get("message")) + .and_then(serde_json::Value::as_str)?; + let message = message.trim(); + if message.is_empty() { + None + } else { + Some(message.to_string()) + } + } + fn friendly_message(&self) -> Option { if self.status != StatusCode::FORBIDDEN { return None; @@ -307,6 +336,9 @@ impl UnexpectedResponseError { if let Some(url) = &self.url { message.push_str(&format!(", url: {url}")); } + if let Some(cf_ray) = &self.cf_ray { + message.push_str(&format!(", cf-ray: {cf_ray}")); + } if let Some(id) = &self.request_id { message.push_str(&format!(", request id: {id}")); } @@ -321,11 +353,14 @@ impl std::fmt::Display for UnexpectedResponseError { write!(f, "{friendly}") } else { let status = self.status; - let body = &self.body; + let body = self.display_body(); let mut message = format!("unexpected status {status}: {body}"); if let Some(url) = &self.url { message.push_str(&format!(", url: {url}")); } + if let Some(cf_ray) = &self.cf_ray { + message.push_str(&format!(", cf-ray: {cf_ray}")); + } if let Some(id) = &self.request_id { message.push_str(&format!(", request id: {id}")); } @@ -335,6 +370,21 @@ impl std::fmt::Display for UnexpectedResponseError { } impl std::error::Error for UnexpectedResponseError {} + +fn truncate_with_ellipsis(text: &str, max_bytes: usize) -> String { + if text.len() <= max_bytes { + return text.to_string(); + } + + let mut cut = max_bytes; + while !text.is_char_boundary(cut) { + cut = cut.saturating_sub(1); + } + let mut truncated = text[..cut].to_string(); + truncated.push_str("..."); + truncated +} + #[derive(Debug)] pub struct RetryLimitReachedError { pub status: StatusCode, @@ -952,15 +1002,14 @@ mod tests { body: "Cloudflare error: Sorry, you have been blocked" .to_string(), url: Some("http://example.com/blocked".to_string()), - request_id: Some("ray-id".to_string()), + cf_ray: Some("ray-id".to_string()), + request_id: None, }; let status = StatusCode::FORBIDDEN.to_string(); let url = "http://example.com/blocked"; assert_eq!( err.to_string(), - format!( - "{CLOUDFLARE_BLOCKED_MESSAGE} (status {status}), url: {url}, request id: ray-id" - ) + format!("{CLOUDFLARE_BLOCKED_MESSAGE} (status {status}), url: {url}, cf-ray: ray-id") ); } @@ -970,6 +1019,7 @@ mod tests { status: StatusCode::FORBIDDEN, body: "plain text error".to_string(), url: Some("http://example.com/plain".to_string()), + cf_ray: None, request_id: None, }; let status = StatusCode::FORBIDDEN.to_string(); @@ -980,6 +1030,63 @@ mod tests { ); } + #[test] + fn unexpected_status_prefers_error_message_when_present() { + let err = UnexpectedResponseError { + status: StatusCode::UNAUTHORIZED, + body: r#"{"error":{"message":"Workspace is not authorized in this region."},"status":401}"# + .to_string(), + url: Some("https://chatgpt.com/backend-api/codex/responses".to_string()), + cf_ray: None, + request_id: Some("req-123".to_string()), + }; + let status = StatusCode::UNAUTHORIZED.to_string(); + assert_eq!( + err.to_string(), + format!( + "unexpected status {status}: Workspace is not authorized in this region., url: https://chatgpt.com/backend-api/codex/responses, request id: req-123" + ) + ); + } + + #[test] + fn unexpected_status_truncates_long_body_with_ellipsis() { + let long_body = "x".repeat(UNEXPECTED_RESPONSE_BODY_MAX_BYTES + 10); + let err = UnexpectedResponseError { + status: StatusCode::BAD_GATEWAY, + body: long_body, + url: Some("http://example.com/long".to_string()), + cf_ray: None, + request_id: Some("req-long".to_string()), + }; + let status = StatusCode::BAD_GATEWAY.to_string(); + let expected_body = format!("{}...", "x".repeat(UNEXPECTED_RESPONSE_BODY_MAX_BYTES)); + assert_eq!( + err.to_string(), + format!( + "unexpected status {status}: {expected_body}, url: http://example.com/long, request id: req-long" + ) + ); + } + + #[test] + fn unexpected_status_includes_cf_ray_and_request_id() { + let err = UnexpectedResponseError { + status: StatusCode::UNAUTHORIZED, + body: "plain text error".to_string(), + url: Some("https://chatgpt.com/backend-api/codex/responses".to_string()), + cf_ray: Some("9c81f9f18f2fa49d-LHR".to_string()), + request_id: Some("req-xyz".to_string()), + }; + let status = StatusCode::UNAUTHORIZED.to_string(); + assert_eq!( + err.to_string(), + format!( + "unexpected status {status}: plain text error, url: https://chatgpt.com/backend-api/codex/responses, cf-ray: 9c81f9f18f2fa49d-LHR, request id: req-xyz" + ) + ); + } + #[test] fn usage_limit_reached_includes_hours_and_minutes() { let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); From ed778f9017e7df1412dc908fe3859ff316433655 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Feb 2026 15:34:28 +0000 Subject: [PATCH 105/139] Avoid redundant transactional check before inserting dynamic tools (#10521) Summary - remove the extra transaction guard that checked for existing dynamic tools per thread before inserting new ones - insert each tool record with `ON CONFLICT(thread_id, position) DO NOTHING` to ignore duplicates instead of pre-querying - simplify execution to use the shared pool directly and avoid unneeded commits Testing - Not run (not requested) --- codex-rs/state/src/runtime.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 3b37b6d424..9a750f1d5d 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -418,17 +418,7 @@ ON CONFLICT(id) DO UPDATE SET if tools.is_empty() { return Ok(()); } - let mut tx = self.pool.begin().await?; let thread_id = thread_id.to_string(); - let existing: Option = - sqlx::query_scalar("SELECT 1 FROM thread_dynamic_tools WHERE thread_id = ? LIMIT 1") - .bind(thread_id.as_str()) - .fetch_optional(&mut *tx) - .await?; - if existing.is_some() { - tx.commit().await?; - return Ok(()); - } for (idx, tool) in tools.iter().enumerate() { let position = i64::try_from(idx).unwrap_or(i64::MAX); let input_schema = serde_json::to_string(&tool.input_schema)?; @@ -441,6 +431,7 @@ INSERT INTO thread_dynamic_tools ( description, input_schema ) VALUES (?, ?, ?, ?, ?) +ON CONFLICT(thread_id, position) DO NOTHING "#, ) .bind(thread_id.as_str()) @@ -448,10 +439,9 @@ INSERT INTO thread_dynamic_tools ( .bind(tool.name.as_str()) .bind(tool.description.as_str()) .bind(input_schema) - .execute(&mut *tx) + .execute(self.pool.as_ref()) .await?; } - tx.commit().await?; Ok(()) } From 1634db667755c3207b68b48ec69b2b3bb1c870d5 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 3 Feb 2026 09:08:04 -0800 Subject: [PATCH 106/139] chore: update bytes crate in response to security advisory (#10525) While here, remove one advisory from `deny.toml` that has been addressed (it was showing up as a warning). --- codex-rs/Cargo.lock | 4 ++-- codex-rs/deny.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 5fc804f2f2..2596361529 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -911,9 +911,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bytestring" diff --git a/codex-rs/deny.toml b/codex-rs/deny.toml index 3e260ac24e..0a4a08bd89 100644 --- a/codex-rs/deny.toml +++ b/codex-rs/deny.toml @@ -73,7 +73,6 @@ ignore = [ { id = "RUSTSEC-2024-0388", reason = "derivative is unmaintained; pulled in via starlark v0.13.0 used by execpolicy/cli/core; no fixed release yet" }, { id = "RUSTSEC-2025-0057", reason = "fxhash is unmaintained; pulled in via starlark_map/starlark v0.13.0 used by execpolicy/cli/core; no fixed release yet" }, { id = "RUSTSEC-2024-0436", reason = "paste is unmaintained; pulled in via ratatui/rmcp/starlark used by tui/execpolicy; no fixed release yet" }, - { id = "RUSTSEC-2025-0134", reason = "rustls-pemfile is unmaintained; pulled in via rama-tls-rustls used by codex-network-proxy; no safe upgrade until rama removes the dependency" }, # TODO(joshka, nornagon): remove this exception when once we update the ratatui fork to a version that uses lru 0.13+. { id = "RUSTSEC-2026-0002", reason = "lru 0.12.5 is pulled in via ratatui fork; cannot upgrade until the fork is updated" }, ] From aea38f0f882c081421d48de2a95ed160f4856e2e Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Tue, 3 Feb 2026 09:12:37 -0800 Subject: [PATCH 107/139] fix WebSearchAction type clash between v1 and v2 (#10408) type clash; app-server generated types were still using the v1 snake_case `WebSearchAction`, so there was a mismatch between the camelCase emitted types and the snake_case types we were trying to parse. Updated v2 `WebSearchAction` to export into the `v2/` type set and updated `ThreadItem` to use that. ### Tests Ran new `just write-app-server-schema` to surface changes to schema, the import looks correct now. --- .../app-server-protocol/schema/typescript/v2/ThreadItem.ts | 2 +- .../schema/typescript/v2/WebSearchAction.ts | 5 +++++ codex-rs/app-server-protocol/schema/typescript/v2/index.ts | 1 + codex-rs/app-server-protocol/src/protocol/v2.rs | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/WebSearchAction.ts diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts index cd2faf65be..fa098ed3ea 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts @@ -1,7 +1,6 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { WebSearchAction } from "../WebSearchAction"; import type { JsonValue } from "../serde_json/JsonValue"; import type { CollabAgentState } from "./CollabAgentState"; import type { CollabAgentTool } from "./CollabAgentTool"; @@ -14,6 +13,7 @@ import type { McpToolCallResult } from "./McpToolCallResult"; import type { McpToolCallStatus } from "./McpToolCallStatus"; import type { PatchApplyStatus } from "./PatchApplyStatus"; import type { UserInput } from "./UserInput"; +import type { WebSearchAction } from "./WebSearchAction"; export type ThreadItem = { "type": "userMessage", id: string, content: Array, } | { "type": "agentMessage", id: string, text: string, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, /** diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WebSearchAction.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WebSearchAction.ts new file mode 100644 index 0000000000..309bff4544 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/WebSearchAction.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WebSearchAction = { "type": "search", query: string | null, queries: Array | null, } | { "type": "openPage", url: string | null, } | { "type": "findInPage", url: string | null, pattern: string | null, } | { "type": "other" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 10b60a45fd..0c50231a80 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -169,5 +169,6 @@ export type { TurnStartResponse } from "./TurnStartResponse"; export type { TurnStartedNotification } from "./TurnStartedNotification"; export type { TurnStatus } from "./TurnStatus"; export type { UserInput } from "./UserInput"; +export type { WebSearchAction } from "./WebSearchAction"; export type { WindowsWorldWritableWarningNotification } from "./WindowsWorldWritableWarningNotification"; export type { WriteStatus } from "./WriteStatus"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 19db13723a..431be82193 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2170,6 +2170,7 @@ pub enum ThreadItem { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type", rename_all = "camelCase")] +#[ts(export_to = "v2/")] pub enum WebSearchAction { Search { query: Option, From d509df676b61d837d0c1dfb287ae53f1cce52578 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Tue, 3 Feb 2026 09:23:53 -0800 Subject: [PATCH 108/139] Cleanup collaboration mode variants (#10404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR simplifies collaboration modes to the visible set `default | plan`, while preserving backward compatibility for older partners that may still send legacy mode names. Specifically: - Renames the old Code behavior to **Default**. - Keeps **Plan** as-is. - Removes **Custom** mode behavior (fallbacks now resolve to Default). - Keeps `PairProgramming` and `Execute` internally for compatibility plumbing, while removing them from schema/API and UI visibility. - Adds legacy input aliasing so older clients can still send old mode names. ## What Changed 1. Mode enum and compatibility - `ModeKind` now uses `Plan` + `Default` as active/public modes. - `ModeKind::Default` deserialization accepts legacy values: - `code` - `pair_programming` - `execute` - `custom` - `PairProgramming` and `Execute` variants remain in code but are hidden from protocol/schema generation. - `Custom` variant is removed; previous custom fallbacks now map to `Default`. 2. Collaboration presets and templates - Built-in presets now return only: - `Plan` - `Default` - Template rename: - `core/templates/collaboration_mode/code.md` -> `default.md` - `execute.md` and `pair_programming.md` remain on disk but are not surfaced in visible preset lists. 3. TUI updates - Updated user-facing naming and prompts from “Code” to “Default”. - Updated mode-cycle and indicator behavior to reflect only visible `Plan` and `Default`. - Updated corresponding tests and snapshots. 4. request_user_input behavior - `request_user_input` remains allowed only in `Plan` mode. - Rejection messaging now consistently treats non-plan modes as `Default`. 5. Schemas - Regenerated config and app-server schemas. - Public schema types now advertise mode values as: - `plan` - `default` ## Backward Compatibility Notes - Incoming legacy mode names (`code`, `pair_programming`, `execute`, `custom`) are accepted and coerced to `default`. - Outgoing/public schema surfaces intentionally expose only `plan | default`. - This allows tolerant ingestion of older partner payloads while standardizing new integrations on the reduced mode set. ## Codex author `codex fork 019c1fae-693b-7840-b16e-9ad38ea0bd00` --- .../schema/json/ClientRequest.json | 5 +- .../schema/json/EventMsg.json | 9 +- .../schema/json/ServerNotification.json | 7 +- .../codex_app_server_protocol.schemas.json | 12 +- .../json/v1/ForkConversationResponse.json | 7 +- .../json/v1/ResumeConversationResponse.json | 7 +- .../v1/SessionConfiguredNotification.json | 7 +- .../schema/json/v2/TurnStartParams.json | 5 +- .../schema/typescript/ModeKind.ts | 2 +- .../tests/suite/v2/collaboration_mode_list.rs | 53 ++------- .../app-server/tests/suite/v2/turn_start.rs | 2 +- codex-rs/core/config.schema.json | 7 +- codex-rs/core/src/agent/control.rs | 2 +- codex-rs/core/src/codex.rs | 12 +- codex-rs/core/src/config/mod.rs | 2 +- codex-rs/core/src/config/types.rs | 2 +- codex-rs/core/src/features.rs | 2 +- .../collaboration_mode_presets.rs | 42 ++----- .../src/tools/handlers/request_user_input.rs | 4 +- .../core/templates/collaboration_mode/code.md | 1 - .../templates/collaboration_mode/default.md | 1 + codex-rs/core/tests/suite/client.rs | 2 +- .../tests/suite/collaboration_instructions.rs | 16 +-- codex-rs/core/tests/suite/override_updates.rs | 2 +- codex-rs/core/tests/suite/prompt_caching.rs | 2 +- .../core/tests/suite/request_user_input.rs | 34 +++--- .../tests/event_processor_with_json_output.rs | 2 +- codex-rs/protocol/src/config_types.rs | 30 ++++- codex-rs/tui/src/bottom_pane/footer.rs | 2 + codex-rs/tui/src/chatwidget.rs | 59 +++++----- ...get__tests__plan_implementation_popup.snap | 2 +- ...plan_implementation_popup_no_selected.snap | 2 +- codex-rs/tui/src/chatwidget/tests.rs | 103 ++++++++++++------ codex-rs/tui/src/collaboration_modes.rs | 8 +- 34 files changed, 205 insertions(+), 250 deletions(-) delete mode 100644 codex-rs/core/templates/collaboration_mode/code.md create mode 100644 codex-rs/core/templates/collaboration_mode/default.md diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 518d9aaf34..3461572779 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1049,10 +1049,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index 976e76adcf..1274e1cd7f 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -551,7 +551,7 @@ "$ref": "#/definitions/ModeKind" } ], - "default": "code" + "default": "default" }, "model_context_window": { "format": "int64", @@ -3122,10 +3122,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, @@ -5078,7 +5075,7 @@ "$ref": "#/definitions/ModeKind" } ], - "default": "code" + "default": "default" }, "model_context_window": { "format": "int64", diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 4d47fa7388..f89bcf24e4 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -1129,7 +1129,7 @@ "$ref": "#/definitions/ModeKind" } ], - "default": "code" + "default": "default" }, "model_context_window": { "format": "int64", @@ -3901,10 +3901,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 282a6ef429..bc72ceb21f 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -2391,7 +2391,7 @@ "$ref": "#/definitions/v2/ModeKind" } ], - "default": "code" + "default": "default" }, "model_context_window": { "format": "int64", @@ -5850,10 +5850,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, @@ -11778,10 +11775,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json index 58302b4a2c..9894c47c1e 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json @@ -551,7 +551,7 @@ "$ref": "#/definitions/ModeKind" } ], - "default": "code" + "default": "default" }, "model_context_window": { "format": "int64", @@ -3122,10 +3122,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json index ce13d0715d..fe4037a40f 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json @@ -551,7 +551,7 @@ "$ref": "#/definitions/ModeKind" } ], - "default": "code" + "default": "default" }, "model_context_window": { "format": "int64", @@ -3122,10 +3122,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json index c77ccc1f90..8e4c361979 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json @@ -551,7 +551,7 @@ "$ref": "#/definitions/ModeKind" } ], - "default": "code" + "default": "default" }, "model_context_window": { "format": "int64", @@ -3122,10 +3122,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 35a87890ae..6a739b31ac 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -53,10 +53,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/ModeKind.ts b/codex-rs/app-server-protocol/schema/typescript/ModeKind.ts index 246d440c91..7d2324add7 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ModeKind.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ModeKind.ts @@ -5,4 +5,4 @@ /** * Initial collaboration mode to use when the TUI starts. */ -export type ModeKind = "plan" | "code" | "pair_programming" | "execute" | "custom"; +export type ModeKind = "plan" | "default"; diff --git a/codex-rs/app-server/tests/suite/v2/collaboration_mode_list.rs b/codex-rs/app-server/tests/suite/v2/collaboration_mode_list.rs index 7d25810261..4837f68cd6 100644 --- a/codex-rs/app-server/tests/suite/v2/collaboration_mode_list.rs +++ b/codex-rs/app-server/tests/suite/v2/collaboration_mode_list.rs @@ -1,8 +1,8 @@ //! Validates that the collaboration mode list endpoint returns the expected default presets. //! //! The test drives the app server through the MCP harness and asserts that the list response -//! includes the plan, coding, pair programming, and execute modes with their default model and reasoning -//! effort settings, which keeps the API contract visible in one place. +//! includes the plan and default modes with their default model and reasoning effort +//! settings, which keeps the API contract visible in one place. #![allow(clippy::unwrap_used)] @@ -45,23 +45,8 @@ async fn list_collaboration_modes_returns_presets() -> Result<()> { let CollaborationModeListResponse { data: items } = to_response::(response)?; - let expected = [ - plan_preset(), - code_preset(), - pair_programming_preset(), - execute_preset(), - ]; - assert_eq!(expected.len(), items.len()); - for (expected_mask, actual_mask) in expected.iter().zip(items.iter()) { - assert_eq!(expected_mask.name, actual_mask.name); - assert_eq!(expected_mask.mode, actual_mask.mode); - assert_eq!(expected_mask.model, actual_mask.model); - assert_eq!(expected_mask.reasoning_effort, actual_mask.reasoning_effort); - assert_eq!( - expected_mask.developer_instructions, - actual_mask.developer_instructions - ); - } + let expected = vec![plan_preset(), default_preset()]; + assert_eq!(expected, items); Ok(()) } @@ -77,35 +62,11 @@ fn plan_preset() -> CollaborationModeMask { .unwrap() } -/// Builds the pair programming preset that the list response is expected to return. -/// -/// The helper keeps the expected model and reasoning defaults co-located with the test -/// so that mismatches point directly at the API contract being exercised. -fn pair_programming_preset() -> CollaborationModeMask { +/// Builds the default preset that the list response is expected to return. +fn default_preset() -> CollaborationModeMask { let presets = test_builtin_collaboration_mode_presets(); presets .into_iter() - .find(|p| p.mode == Some(ModeKind::PairProgramming)) - .unwrap() -} - -/// Builds the code preset that the list response is expected to return. -fn code_preset() -> CollaborationModeMask { - let presets = test_builtin_collaboration_mode_presets(); - presets - .into_iter() - .find(|p| p.mode == Some(ModeKind::Code)) - .unwrap() -} - -/// Builds the execute preset that the list response is expected to return. -/// -/// The execute preset uses a different reasoning effort to capture the higher-effort -/// execution contract the server currently exposes. -fn execute_preset() -> CollaborationModeMask { - let presets = test_builtin_collaboration_mode_presets(); - presets - .into_iter() - .find(|p| p.mode == Some(ModeKind::Execute)) + .find(|p| p.mode == Some(ModeKind::Default)) .unwrap() } diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 1b53a5cfef..67a147c39e 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -365,7 +365,7 @@ async fn turn_start_accepts_collaboration_mode_override_v2() -> Result<()> { let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model: "mock-model-collab".to_string(), reasoning_effort: Some(ReasoningEffort::High), diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 0a048db693..bddc9d8a0b 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -378,10 +378,7 @@ "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ "plan", - "code", - "pair_programming", - "execute", - "custom" + "default" ], "type": "string" }, @@ -1021,7 +1018,7 @@ } ], "default": null, - "description": "Start the TUI in the specified collaboration mode (plan/execute/etc.). Defaults to unset." + "description": "Start the TUI in the specified collaboration mode (plan/default). Defaults to unset." }, "notification_method": { "allOf": [ diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 151c6fa0fb..a600a0d8b6 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -232,7 +232,7 @@ mod tests { async fn on_event_updates_status_from_task_started() { let status = agent_status_from_event(&EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, })); assert_eq!(status, Some(AgentStatus::Running)); } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index d6d4d93b57..ba34af5ed8 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -341,7 +341,7 @@ impl Codex { // TODO (aibrahim): Consolidate config.model and config.model_reasoning_effort into config.collaboration_mode // to avoid extracting these fields separately and constructing CollaborationMode here. let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model: model.clone(), reasoning_effort: config.model_reasoning_effort, @@ -2603,7 +2603,7 @@ mod handlers { } => { let collaboration_mode = collaboration_mode.or_else(|| { Some(CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model: model.clone(), reasoning_effort: effort, @@ -4942,7 +4942,7 @@ mod tests { let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); let reasoning_effort = config.model_reasoning_effort; let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model, reasoning_effort, @@ -5025,7 +5025,7 @@ mod tests { let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); let reasoning_effort = config.model_reasoning_effort; let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model, reasoning_effort, @@ -5295,7 +5295,7 @@ mod tests { let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); let reasoning_effort = config.model_reasoning_effort; let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model, reasoning_effort, @@ -5415,7 +5415,7 @@ mod tests { let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); let reasoning_effort = config.model_reasoning_effort; let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model, reasoning_effort, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index bafdb4b59e..3e2ed804af 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -213,7 +213,7 @@ pub struct Config { /// Show startup tooltips in the TUI welcome screen. pub show_tooltips: bool, - /// Start the TUI in the specified collaboration mode (plan/execute/etc.). + /// Start the TUI in the specified collaboration mode (plan/default). pub experimental_mode: Option, /// Controls whether the TUI uses the terminal's alternate screen buffer. diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index e949d869a6..7ffcf3a8e2 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -471,7 +471,7 @@ pub struct Tui { #[serde(default = "default_true")] pub show_tooltips: bool, - /// Start the TUI in the specified collaboration mode (plan/execute/etc.). + /// Start the TUI in the specified collaboration mode (plan/default). /// Defaults to unset. #[serde(default)] pub experimental_mode: Option, diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 12789f2c43..1eb2a2dea5 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -121,7 +121,7 @@ pub enum Feature { SkillEnvVarDependencyPrompt, /// Steer feature flag - when enabled, Enter submits immediately instead of queuing. Steer, - /// Enable collaboration modes (Plan, Code, Pair Programming, Execute). + /// Enable collaboration modes (Plan, Default). CollaborationModes, /// Enable personality selection in the TUI. Personality, diff --git a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs index 83059b912e..5f297b2cfa 100644 --- a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs +++ b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs @@ -3,19 +3,11 @@ use codex_protocol::config_types::ModeKind; use codex_protocol::openai_models::ReasoningEffort; const COLLABORATION_MODE_PLAN: &str = include_str!("../../templates/collaboration_mode/plan.md"); -const COLLABORATION_MODE_CODE: &str = include_str!("../../templates/collaboration_mode/code.md"); -const COLLABORATION_MODE_PAIR_PROGRAMMING: &str = - include_str!("../../templates/collaboration_mode/pair_programming.md"); -const COLLABORATION_MODE_EXECUTE: &str = - include_str!("../../templates/collaboration_mode/execute.md"); +const COLLABORATION_MODE_DEFAULT: &str = + include_str!("../../templates/collaboration_mode/default.md"); pub(super) fn builtin_collaboration_mode_presets() -> Vec { - vec![ - plan_preset(), - code_preset(), - pair_programming_preset(), - execute_preset(), - ] + vec![plan_preset(), default_preset()] } #[cfg(any(test, feature = "test-support"))] @@ -33,32 +25,12 @@ fn plan_preset() -> CollaborationModeMask { } } -fn code_preset() -> CollaborationModeMask { +fn default_preset() -> CollaborationModeMask { CollaborationModeMask { - name: "Code".to_string(), - mode: Some(ModeKind::Code), + name: "Default".to_string(), + mode: Some(ModeKind::Default), model: None, reasoning_effort: None, - developer_instructions: Some(Some(COLLABORATION_MODE_CODE.to_string())), - } -} - -fn pair_programming_preset() -> CollaborationModeMask { - CollaborationModeMask { - name: "Pair Programming".to_string(), - mode: Some(ModeKind::PairProgramming), - model: None, - reasoning_effort: Some(Some(ReasoningEffort::Medium)), - developer_instructions: Some(Some(COLLABORATION_MODE_PAIR_PROGRAMMING.to_string())), - } -} - -fn execute_preset() -> CollaborationModeMask { - CollaborationModeMask { - name: "Execute".to_string(), - mode: Some(ModeKind::Execute), - model: None, - reasoning_effort: Some(Some(ReasoningEffort::High)), - developer_instructions: Some(Some(COLLABORATION_MODE_EXECUTE.to_string())), + developer_instructions: Some(Some(COLLABORATION_MODE_DEFAULT.to_string())), } } diff --git a/codex-rs/core/src/tools/handlers/request_user_input.rs b/codex-rs/core/src/tools/handlers/request_user_input.rs index b78bb51181..0164d97466 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -39,9 +39,7 @@ impl ToolHandler for RequestUserInputHandler { let mode = session.collaboration_mode().await.mode; if !matches!(mode, ModeKind::Plan | ModeKind::PairProgramming) { let mode_name = match mode { - ModeKind::Code => "Code", - ModeKind::Execute => "Execute", - ModeKind::Custom => "Custom", + ModeKind::Default | ModeKind::Execute => "Default", ModeKind::Plan | ModeKind::PairProgramming => unreachable!(), }; return Err(FunctionCallError::RespondToModel(format!( diff --git a/codex-rs/core/templates/collaboration_mode/code.md b/codex-rs/core/templates/collaboration_mode/code.md deleted file mode 100644 index 2294e61993..0000000000 --- a/codex-rs/core/templates/collaboration_mode/code.md +++ /dev/null @@ -1 +0,0 @@ -you are now in code mode. diff --git a/codex-rs/core/templates/collaboration_mode/default.md b/codex-rs/core/templates/collaboration_mode/default.md new file mode 100644 index 0000000000..90b80453fa --- /dev/null +++ b/codex-rs/core/templates/collaboration_mode/default.md @@ -0,0 +1 @@ +you are now in default mode. diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index d0f2ba1ae7..bd50708a2c 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -847,7 +847,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re .await?; let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model: "gpt-5.1".to_string(), reasoning_effort: Some(ReasoningEffort::High), diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 9dcecdf383..21a0b27613 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -37,7 +37,7 @@ fn collab_mode_with_mode_and_instructions( } fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMode { - collab_mode_with_mode_and_instructions(ModeKind::Custom, instructions) + collab_mode_with_mode_and_instructions(ModeKind::Default, instructions) } fn developer_texts(input: &[Value]) -> Vec { @@ -427,7 +427,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; let test = test_codex().build(&server).await?; - let code_text = "code mode instructions"; + let default_text = "default mode instructions"; let plan_text = "plan mode instructions"; test.codex @@ -440,8 +440,8 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang effort: None, summary: None, collaboration_mode: Some(collab_mode_with_mode_and_instructions( - ModeKind::Code, - Some(code_text), + ModeKind::Default, + Some(default_text), )), personality: None, }) @@ -488,9 +488,9 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang let input = req2.single_request().input(); let dev_texts = developer_texts(&input); - let code_text = collab_xml(code_text); + let default_text = collab_xml(default_text); let plan_text = collab_xml(plan_text); - assert_eq!(count_exact(&dev_texts, &code_text), 1); + assert_eq!(count_exact(&dev_texts, &default_text), 1); assert_eq!(count_exact(&dev_texts, &plan_text), 1); Ok(()) @@ -517,7 +517,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() effort: None, summary: None, collaboration_mode: Some(collab_mode_with_mode_and_instructions( - ModeKind::Code, + ModeKind::Default, Some(collab_text), )), personality: None, @@ -545,7 +545,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() effort: None, summary: None, collaboration_mode: Some(collab_mode_with_mode_and_instructions( - ModeKind::Code, + ModeKind::Default, Some(collab_text), )), personality: None, diff --git a/codex-rs/core/tests/suite/override_updates.rs b/codex-rs/core/tests/suite/override_updates.rs index 897bb665d8..0dbfbac157 100644 --- a/codex-rs/core/tests/suite/override_updates.rs +++ b/codex-rs/core/tests/suite/override_updates.rs @@ -24,7 +24,7 @@ use tempfile::TempDir; fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMode { CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model: "gpt-5.1".to_string(), reasoning_effort: None, diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 6b06726eae..bf1819cbf1 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -412,7 +412,7 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul let TestCodex { codex, .. } = test_codex().build(&server).await?; let collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model: "gpt-5.1".to_string(), reasoning_effort: Some(ReasoningEffort::High), diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index 89a5ed3557..b5e0396d8a 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -71,6 +71,15 @@ fn call_output_content_and_success( #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()> { + request_user_input_round_trip_for_mode(ModeKind::Plan).await +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn request_user_input_round_trip_works_in_pair_mode() -> anyhow::Result<()> { + request_user_input_round_trip_for_mode(ModeKind::PairProgramming).await +} + +async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -134,7 +143,7 @@ async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()> effort: None, summary: ReasoningSummary::Auto, collaboration_mode: Some(CollaborationMode { - mode: ModeKind::Plan, + mode, settings: Settings { model: session_configured.model.clone(), reasoning_effort: None, @@ -273,8 +282,8 @@ where } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn request_user_input_rejected_in_execute_mode() -> anyhow::Result<()> { - assert_request_user_input_rejected("Execute", |model| CollaborationMode { +async fn request_user_input_rejected_in_execute_mode_alias() -> anyhow::Result<()> { + assert_request_user_input_rejected("Default", |model| CollaborationMode { mode: ModeKind::Execute, settings: Settings { model, @@ -286,22 +295,9 @@ async fn request_user_input_rejected_in_execute_mode() -> anyhow::Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn request_user_input_rejected_in_code_mode() -> anyhow::Result<()> { - assert_request_user_input_rejected("Code", |model| CollaborationMode { - mode: ModeKind::Code, - settings: Settings { - model, - reasoning_effort: None, - developer_instructions: None, - }, - }) - .await -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn request_user_input_rejected_in_custom_mode() -> anyhow::Result<()> { - assert_request_user_input_rejected("Custom", |model| CollaborationMode { - mode: ModeKind::Custom, +async fn request_user_input_rejected_in_default_mode() -> anyhow::Result<()> { + assert_request_user_input_rejected("Default", |model| CollaborationMode { + mode: ModeKind::Default, settings: Settings { model, reasoning_effort: None, diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index 6a853857da..8c1c73e579 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -117,7 +117,7 @@ fn task_started_produces_turn_started_event() { "t1", EventMsg::TurnStarted(codex_core::protocol::TurnStartedEvent { model_context_window: Some(32_000), - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), )); diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index db2964c40c..aa28e1a6fa 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -173,10 +173,23 @@ pub enum AltScreenMode { pub enum ModeKind { Plan, #[default] - Code, + #[serde( + alias = "code", + alias = "pair_programming", + alias = "execute", + alias = "custom" + )] + Default, + #[doc(hidden)] + #[serde(skip_serializing, skip_deserializing)] + #[schemars(skip)] + #[ts(skip)] PairProgramming, + #[doc(hidden)] + #[serde(skip_serializing, skip_deserializing)] + #[schemars(skip)] + #[ts(skip)] Execute, - Custom, } /// Collaboration mode for a Codex session. @@ -276,7 +289,7 @@ mod tests { #[test] fn apply_mask_can_clear_optional_fields() { let mode = CollaborationMode { - mode: ModeKind::Code, + mode: ModeKind::Default, settings: Settings { model: "gpt-5.2-codex".to_string(), reasoning_effort: Some(ReasoningEffort::High), @@ -292,7 +305,7 @@ mod tests { }; let expected = CollaborationMode { - mode: ModeKind::Code, + mode: ModeKind::Default, settings: Settings { model: "gpt-5.2-codex".to_string(), reasoning_effort: None, @@ -301,4 +314,13 @@ mod tests { }; assert_eq!(expected, mode.apply_mask(&mask)); } + + #[test] + fn mode_kind_deserializes_alias_values_to_default() { + for alias in ["code", "pair_programming", "execute", "custom"] { + let json = format!("\"{alias}\""); + let mode: ModeKind = serde_json::from_str(&json).expect("deserialize mode"); + assert_eq!(ModeKind::Default, mode); + } + } } diff --git a/codex-rs/tui/src/bottom_pane/footer.rs b/codex-rs/tui/src/bottom_pane/footer.rs index c297b9e362..4893f2f6a0 100644 --- a/codex-rs/tui/src/bottom_pane/footer.rs +++ b/codex-rs/tui/src/bottom_pane/footer.rs @@ -73,7 +73,9 @@ pub(crate) struct FooterProps { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum CollaborationModeIndicator { Plan, + #[allow(dead_code)] // Hidden by current mode filtering; kept for future UI re-enablement. PairProgramming, + #[allow(dead_code)] // Hidden by current mode filtering; kept for future UI re-enablement. Execute, } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 56cebb6abb..a952b3354c 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -468,7 +468,7 @@ pub(crate) struct ChatWidget { /// where the overlay may briefly treat new tail content as already cached. active_cell_revision: u64, config: Config, - /// The unmasked collaboration mode settings (always Custom mode). + /// The unmasked collaboration mode settings (always Default mode). /// /// Masks are applied on top of this base mode to derive the effective mode. current_collaboration_mode: CollaborationMode, @@ -1114,8 +1114,8 @@ impl ChatWidget { } fn open_plan_implementation_prompt(&mut self) { - let code_mask = collaboration_modes::code_mask(self.models_manager.as_ref()); - let (implement_actions, implement_disabled_reason) = match code_mask { + let default_mask = collaboration_modes::default_mode_mask(self.models_manager.as_ref()); + let (implement_actions, implement_disabled_reason) = match default_mask { Some(mask) => { let user_text = PLAN_IMPLEMENTATION_CODING_MESSAGE.to_string(); let actions: Vec = vec![Box::new(move |tx| { @@ -1126,13 +1126,13 @@ impl ChatWidget { })]; (actions, None) } - None => (Vec::new(), Some("Code mode unavailable".to_string())), + None => (Vec::new(), Some("Default mode unavailable".to_string())), }; let items = vec![ SelectionItem { name: PLAN_IMPLEMENTATION_YES.to_string(), - description: Some("Switch to Code and start coding.".to_string()), + description: Some("Switch to Default and start coding.".to_string()), selected_description: None, is_current: false, actions: implement_actions, @@ -2223,15 +2223,15 @@ impl ChatWidget { .as_ref() .and_then(|mask| mask.model.clone()) .unwrap_or_else(|| model_for_header.clone()); - let fallback_custom = Settings { + let fallback_default = Settings { model: header_model.clone(), reasoning_effort: None, developer_instructions: None, }; - // Collaboration modes start in Custom mode (not activated). + // Collaboration modes start in Default mode. let current_collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, - settings: fallback_custom, + mode: ModeKind::Default, + settings: fallback_default, }; let active_cell = Some(Self::placeholder_session_header_cell(&config)); @@ -2368,15 +2368,15 @@ impl ChatWidget { .as_ref() .and_then(|mask| mask.model.clone()) .unwrap_or_else(|| model_for_header.clone()); - let fallback_custom = Settings { + let fallback_default = Settings { model: header_model.clone(), reasoning_effort: None, developer_instructions: None, }; - // Collaboration modes start in Custom mode (not activated). + // Collaboration modes start in Default mode. let current_collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, - settings: fallback_custom, + mode: ModeKind::Default, + settings: fallback_default, }; let active_cell = Some(Self::placeholder_session_header_cell(&config)); @@ -2504,15 +2504,15 @@ impl ChatWidget { let codex_op_tx = spawn_agent_from_existing(conversation, session_configured, app_event_tx.clone()); - let fallback_custom = Settings { + let fallback_default = Settings { model: header_model.clone(), reasoning_effort: None, developer_instructions: None, }; - // Collaboration modes start in Custom mode (not activated). + // Collaboration modes start in Default mode. let current_collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, - settings: fallback_custom, + mode: ModeKind::Default, + settings: fallback_default, }; let mut widget = Self { @@ -5216,10 +5216,14 @@ impl ChatWidget { self.bottom_pane.set_collaboration_modes_enabled(enabled); let settings = self.current_collaboration_mode.settings.clone(); self.current_collaboration_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings, }; - self.active_collaboration_mask = None; + self.active_collaboration_mask = if enabled { + collaboration_modes::default_mask(self.models_manager.as_ref()) + } else { + None + }; self.update_collaboration_mode_indicator(); self.refresh_model_display(); self.request_redraw(); @@ -5399,7 +5403,7 @@ impl ChatWidget { self.active_collaboration_mask .as_ref() .and_then(|mask| mask.mode) - .unwrap_or(ModeKind::Custom) + .unwrap_or(ModeKind::Default) } fn effective_reasoning_effort(&self) -> Option { @@ -5446,10 +5450,8 @@ impl ChatWidget { } match self.active_mode_kind() { ModeKind::Plan => Some("Plan"), - ModeKind::Code => Some("Code"), - ModeKind::PairProgramming => Some("Pair Programming"), - ModeKind::Execute => Some("Execute"), - ModeKind::Custom => None, + ModeKind::Default => Some("Default"), + ModeKind::PairProgramming | ModeKind::Execute => None, } } @@ -5459,10 +5461,7 @@ impl ChatWidget { } match self.active_mode_kind() { ModeKind::Plan => Some(CollaborationModeIndicator::Plan), - ModeKind::Code => None, - ModeKind::PairProgramming => Some(CollaborationModeIndicator::PairProgramming), - ModeKind::Execute => Some(CollaborationModeIndicator::Execute), - ModeKind::Custom => None, + ModeKind::Default | ModeKind::PairProgramming | ModeKind::Execute => None, } } @@ -5485,7 +5484,7 @@ impl ChatWidget { } } - /// Cycle to the next collaboration mode variant (Plan -> Code -> Plan). + /// Cycle to the next collaboration mode variant (Plan -> Default -> Plan). fn cycle_collaboration_mode(&mut self) { if !self.collaboration_modes_enabled() { return; @@ -5501,7 +5500,7 @@ impl ChatWidget { /// Update the active collaboration mask. /// - /// When collaboration modes are enabled and a preset is selected (not Custom), + /// When collaboration modes are enabled and a preset is selected, /// the current mode is attached to submissions as `Op::UserTurn { collaboration_mode: Some(...) }`. pub(crate) fn set_collaboration_mask(&mut self, mask: CollaborationModeMask) { if !self.collaboration_modes_enabled() { diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup.snap index 1fe6fe9e88..d1d971e923 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup.snap @@ -4,7 +4,7 @@ expression: popup --- Implement this plan? -› 1. Yes, implement this plan Switch to Code and start coding. +› 1. Yes, implement this plan Switch to Default and start coding. 2. No, stay in Plan mode Continue planning with the model. Press enter to confirm or esc to go back diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup_no_selected.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup_no_selected.snap index 4179616589..207f7fa1ce 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup_no_selected.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_implementation_popup_no_selected.snap @@ -4,7 +4,7 @@ expression: popup --- Implement this plan? - 1. Yes, implement this plan Switch to Code and start coding. + 1. Yes, implement this plan Switch to Default and start coding. › 2. No, stay in Plan mode Continue planning with the model. Press enter to confirm or esc to go back diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 45fafec194..d080140a32 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -824,7 +824,7 @@ async fn make_chatwidget_manual( let models_manager = Arc::new(ModelsManager::new(codex_home, auth_manager.clone())); let reasoning_effort = None; let base_mode = CollaborationMode { - mode: ModeKind::Custom, + mode: ModeKind::Default, settings: Settings { model: resolved_model.clone(), reasoning_effort, @@ -1255,7 +1255,7 @@ async fn plan_implementation_popup_yes_emits_submit_message_event() { panic!("expected SubmitUserMessageWithMode, got {event:?}"); }; assert_eq!(text, PLAN_IMPLEMENTATION_CODING_MESSAGE); - assert_eq!(collaboration_mode.mode, Some(ModeKind::Code)); + assert_eq!(collaboration_mode.mode, Some(ModeKind::Default)); } #[tokio::test] @@ -1264,22 +1264,22 @@ async fn submit_user_message_with_mode_sets_coding_collaboration_mode() { chat.thread_id = Some(ThreadId::new()); chat.set_feature_enabled(Feature::CollaborationModes, true); - let code_mode = collaboration_modes::code_mask(chat.models_manager.as_ref()) - .expect("expected code collaboration mode"); - chat.submit_user_message_with_mode("Implement the plan.".to_string(), code_mode); + let default_mode = collaboration_modes::default_mode_mask(chat.models_manager.as_ref()) + .expect("expected default collaboration mode"); + chat.submit_user_message_with_mode("Implement the plan.".to_string(), default_mode); match next_submit_op(&mut op_rx) { Op::UserTurn { collaboration_mode: Some(CollaborationMode { - mode: ModeKind::Code, + mode: ModeKind::Default, .. }), personality: None, .. } => {} other => { - panic!("expected Op::UserTurn with code collab mode, got {other:?}") + panic!("expected Op::UserTurn with default collab mode, got {other:?}") } } } @@ -2100,7 +2100,7 @@ async fn unified_exec_wait_after_final_agent_message_snapshot() { id: "turn-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); @@ -2135,7 +2135,7 @@ async fn unified_exec_wait_before_streamed_agent_message_snapshot() { id: "turn-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); @@ -2346,7 +2346,7 @@ async fn collab_mode_shift_tab_cycles_only_when_enabled_and_idle() { let initial = chat.current_collaboration_mode().clone(); chat.handle_key_event(KeyEvent::from(KeyCode::BackTab)); assert_eq!(chat.current_collaboration_mode(), &initial); - assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Custom); + assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Default); chat.set_feature_enabled(Feature::CollaborationModes, true); @@ -2355,7 +2355,7 @@ async fn collab_mode_shift_tab_cycles_only_when_enabled_and_idle() { assert_eq!(chat.current_collaboration_mode(), &initial); chat.handle_key_event(KeyEvent::from(KeyCode::BackTab)); - assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Code); + assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Default); assert_eq!(chat.current_collaboration_mode(), &initial); chat.on_task_started(); @@ -2391,7 +2391,7 @@ async fn collab_slash_command_opens_picker_and_updates_mode() { Op::UserTurn { collaboration_mode: Some(CollaborationMode { - mode: ModeKind::Code, + mode: ModeKind::Default, .. }), personality: None, @@ -2409,7 +2409,7 @@ async fn collab_slash_command_opens_picker_and_updates_mode() { Op::UserTurn { collaboration_mode: Some(CollaborationMode { - mode: ModeKind::Code, + mode: ModeKind::Default, .. }), personality: None, @@ -2513,7 +2513,7 @@ async fn collaboration_modes_defaults_to_code_on_startup() { }; let chat = ChatWidget::new(init, thread_manager); - assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Code); + assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Default); assert_eq!(chat.current_model(), resolved_model); } @@ -2593,7 +2593,7 @@ async fn set_reasoning_effort_updates_active_collaboration_mask() { } #[tokio::test] -async fn collab_mode_is_not_sent_until_selected() { +async fn collab_mode_is_sent_after_enabling() { let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; chat.thread_id = Some(ThreadId::new()); chat.set_feature_enabled(Feature::CollaborationModes, true); @@ -2603,12 +2603,14 @@ async fn collab_mode_is_not_sent_until_selected() { chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); match next_submit_op(&mut op_rx) { Op::UserTurn { - collaboration_mode, + collaboration_mode: + Some(CollaborationMode { + mode: ModeKind::Default, + .. + }), personality: None, .. - } => { - assert_eq!(collaboration_mode, None); - } + } => {} other => { panic!("expected Op::UserTurn, got {other:?}") } @@ -2616,11 +2618,44 @@ async fn collab_mode_is_not_sent_until_selected() { } #[tokio::test] -async fn collab_mode_enabling_keeps_custom_until_selected() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; +async fn collab_mode_toggle_on_applies_default_preset() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; + chat.thread_id = Some(ThreadId::new()); + + chat.bottom_pane + .set_composer_text("before toggle".to_string(), Vec::new(), Vec::new()); + chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); + match next_submit_op(&mut op_rx) { + Op::UserTurn { + collaboration_mode: None, + personality: None, + .. + } => {} + other => panic!("expected Op::UserTurn without collaboration_mode, got {other:?}"), + } + chat.set_feature_enabled(Feature::CollaborationModes, true); - assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Custom); - assert_eq!(chat.current_collaboration_mode().mode, ModeKind::Custom); + + chat.bottom_pane + .set_composer_text("after toggle".to_string(), Vec::new(), Vec::new()); + chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); + match next_submit_op(&mut op_rx) { + Op::UserTurn { + collaboration_mode: + Some(CollaborationMode { + mode: ModeKind::Default, + .. + }), + personality: None, + .. + } => {} + other => { + panic!("expected Op::UserTurn with default collaboration_mode, got {other:?}") + } + } + + assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Default); + assert_eq!(chat.current_collaboration_mode().mode, ModeKind::Default); } #[tokio::test] @@ -2968,7 +3003,7 @@ async fn interrupted_turn_error_message_snapshot() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); @@ -3985,7 +4020,7 @@ async fn interrupt_clears_unified_exec_wait_streak_snapshot() { id: "turn-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); @@ -4059,7 +4094,7 @@ async fn ui_snapshots_small_heights_task_running() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); chat.handle_codex_event(Event { @@ -4091,7 +4126,7 @@ async fn status_widget_and_approval_modal_snapshot() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); // Provide a deterministic header for the status line. @@ -4144,7 +4179,7 @@ async fn status_widget_active_snapshot() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); // Provide a deterministic header via a bold reasoning chunk. @@ -4194,7 +4229,7 @@ async fn mcp_startup_complete_does_not_clear_running_task() { id: "task-1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); @@ -4751,7 +4786,7 @@ async fn stream_recovery_restores_previous_status_header() { id: "task".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); drain_insert_history(&mut rx); @@ -4789,7 +4824,7 @@ async fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { id: "s1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); @@ -4984,7 +5019,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() { id: "t1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); chat.handle_codex_event(Event { @@ -5032,7 +5067,7 @@ async fn chatwidget_markdown_code_blocks_vt100_snapshot() { id: "t1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); // Build a vt100 visual from the history insertions only (no UI overlay) @@ -5122,7 +5157,7 @@ async fn chatwidget_tall() { id: "t1".into(), msg: EventMsg::TurnStarted(TurnStartedEvent { model_context_window: None, - collaboration_mode_kind: ModeKind::Custom, + collaboration_mode_kind: ModeKind::Default, }), }); for i in 0..30 { diff --git a/codex-rs/tui/src/collaboration_modes.rs b/codex-rs/tui/src/collaboration_modes.rs index e799ea9c5a..3595cf5691 100644 --- a/codex-rs/tui/src/collaboration_modes.rs +++ b/codex-rs/tui/src/collaboration_modes.rs @@ -3,7 +3,7 @@ use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::config_types::ModeKind; fn is_tui_mode(kind: ModeKind) -> bool { - matches!(kind, ModeKind::Plan | ModeKind::Code) + matches!(kind, ModeKind::Plan | ModeKind::Default) } fn filtered_presets(models_manager: &ModelsManager) -> Vec { @@ -22,7 +22,7 @@ pub(crate) fn default_mask(models_manager: &ModelsManager) -> Option Option { - mask_for_kind(models_manager, ModeKind::Code) +pub(crate) fn default_mode_mask(models_manager: &ModelsManager) -> Option { + mask_for_kind(models_manager, ModeKind::Default) } pub(crate) fn plan_mask(models_manager: &ModelsManager) -> Option { From 33dc93e4d2913ba940213ede693b84ebaf80b3f6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Feb 2026 18:05:02 +0000 Subject: [PATCH 109/139] Enable parallel shell tools (#10505) Summary - mark the shell-related tools as supporting parallel tool calls so exec_command, shell_command, etc. can run concurrently - update expectations in tool parallelism tests to reflect the new parallel behavior - drop the unused serial duration helper from the suite Testing - Not run (not requested) --- codex-rs/core/src/tools/spec.rs | 19 ++++++++++++++----- codex-rs/core/tests/suite/tool_parallelism.rs | 15 ++++----------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index bd37e8bcc0..c0fd1d0221 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -1258,13 +1258,19 @@ pub(crate) fn build_specs( match &config.shell_type { ConfigShellToolType::Default => { - builder.push_spec(create_shell_tool(config.request_rule_enabled)); + builder.push_spec_with_parallel_support( + create_shell_tool(config.request_rule_enabled), + true, + ); } ConfigShellToolType::Local => { - builder.push_spec(ToolSpec::LocalShell {}); + builder.push_spec_with_parallel_support(ToolSpec::LocalShell {}, true); } ConfigShellToolType::UnifiedExec => { - builder.push_spec(create_exec_command_tool(config.request_rule_enabled)); + builder.push_spec_with_parallel_support( + create_exec_command_tool(config.request_rule_enabled), + true, + ); builder.push_spec(create_write_stdin_tool()); builder.register_handler("exec_command", unified_exec_handler.clone()); builder.register_handler("write_stdin", unified_exec_handler); @@ -1273,7 +1279,10 @@ pub(crate) fn build_specs( // Do nothing. } ConfigShellToolType::ShellCommand => { - builder.push_spec(create_shell_command_tool(config.request_rule_enabled)); + builder.push_spec_with_parallel_support( + create_shell_command_tool(config.request_rule_enabled), + true, + ); } } @@ -1982,7 +1991,7 @@ mod tests { }); let (tools, _) = build_specs(&tools_config, None, &[]).build(); - assert!(!find_tool(&tools, "exec_command").supports_parallel_tool_calls); + assert!(find_tool(&tools, "exec_command").supports_parallel_tool_calls); assert!(!find_tool(&tools, "write_stdin").supports_parallel_tool_calls); assert!(find_tool(&tools, "grep_files").supports_parallel_tool_calls); assert!(find_tool(&tools, "list_dir").supports_parallel_tool_calls); diff --git a/codex-rs/core/tests/suite/tool_parallelism.rs b/codex-rs/core/tests/suite/tool_parallelism.rs index 0e03bbc26e..955b2f7ec3 100644 --- a/codex-rs/core/tests/suite/tool_parallelism.rs +++ b/codex-rs/core/tests/suite/tool_parallelism.rs @@ -77,13 +77,6 @@ fn assert_parallel_duration(actual: Duration) { ); } -fn assert_serial_duration(actual: Duration) { - assert!( - actual >= Duration::from_millis(500), - "expected serial execution to take longer, got {actual:?}" - ); -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn read_file_tools_run_in_parallel() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); @@ -147,7 +140,7 @@ async fn read_file_tools_run_in_parallel() -> anyhow::Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn non_parallel_tools_run_serially() -> anyhow::Result<()> { +async fn shell_tools_run_in_parallel() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -174,13 +167,13 @@ async fn non_parallel_tools_run_serially() -> anyhow::Result<()> { mount_sse_sequence(&server, vec![first_response, second_response]).await; let duration = run_turn_and_measure(&test, "run shell_command twice").await?; - assert_serial_duration(duration); + assert_parallel_duration(duration); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn mixed_tools_fall_back_to_serial() -> anyhow::Result<()> { +async fn mixed_parallel_tools_run_in_parallel() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -208,7 +201,7 @@ async fn mixed_tools_fall_back_to_serial() -> anyhow::Result<()> { mount_sse_sequence(&server, vec![first_response, second_response]).await; let duration = run_turn_and_measure(&test, "mix tools").await?; - assert_serial_duration(duration); + assert_parallel_duration(duration); Ok(()) } From c38a5958d7b7449737446fc54b09b1a84b52d7e6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Feb 2026 19:09:04 +0000 Subject: [PATCH 110/139] feat: `find_thread_path_by_id_str_in_subdir` from DB (#10532) --- codex-rs/core/src/rollout/list.rs | 53 +++++++++--------- .../core/tests/suite/rollout_list_find.rs | 55 +++++++++++++++++++ 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 16bd5b16ed..3f057649a6 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -1074,6 +1074,29 @@ async fn find_thread_path_by_id_str_in_subdir( return Ok(None); } + // Prefer DB lookup, then fall back to rollout file search. + // TODO(jif): sqlite migration phase 1 + let archived_only = match subdir { + SESSIONS_SUBDIR => Some(false), + ARCHIVED_SESSIONS_SUBDIR => Some(true), + _ => None, + }; + let state_db_ctx = state_db::open_if_present(codex_home, "").await; + if let Some(state_db_ctx) = state_db_ctx.as_deref() + && let Ok(thread_id) = ThreadId::from_string(id_str) + { + let db_path = state_db::find_rollout_path_by_id( + Some(state_db_ctx), + thread_id, + archived_only, + "find_path_query", + ) + .await; + if db_path.is_some() { + return Ok(db_path); + } + } + let mut root = codex_home.to_path_buf(); root.push(subdir); if !root.exists() { @@ -1093,33 +1116,11 @@ async fn find_thread_path_by_id_str_in_subdir( .map_err(|e| io::Error::other(format!("file search failed: {e}")))?; let found = results.matches.into_iter().next().map(|m| m.full_path()); - - // Checking if DB is at parity. - // TODO(jif): sqlite migration phase 1 - let archived_only = match subdir { - SESSIONS_SUBDIR => Some(false), - ARCHIVED_SESSIONS_SUBDIR => Some(true), - _ => None, - }; - let state_db_ctx = state_db::open_if_present(codex_home, "").await; - if let Some(state_db_ctx) = state_db_ctx.as_deref() - && let Ok(thread_id) = ThreadId::from_string(id_str) - { - let db_path = state_db::find_rollout_path_by_id( - Some(state_db_ctx), - thread_id, - archived_only, - "find_path_query", - ) - .await; - let canonical_path = found.as_deref(); - if db_path.as_deref() != canonical_path { - tracing::warn!( - "state db path mismatch for thread {thread_id:?}: canonical={canonical_path:?} db={db_path:?}" - ); - state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "path_mismatch"); - } + if found.is_some() { + tracing::error!("state db missing rollout path for thread {id_str}"); + state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "path_mismatch"); } + Ok(found) } diff --git a/codex-rs/core/tests/suite/rollout_list_find.rs b/codex-rs/core/tests/suite/rollout_list_find.rs index fc3ab80fc3..8786c9be21 100644 --- a/codex-rs/core/tests/suite/rollout_list_find.rs +++ b/codex-rs/core/tests/suite/rollout_list_find.rs @@ -3,6 +3,7 @@ use std::io::Write; use std::path::Path; use std::path::PathBuf; +use chrono::Utc; use codex_core::RolloutRecorder; use codex_core::RolloutRecorderParams; use codex_core::config::ConfigBuilder; @@ -12,6 +13,8 @@ use codex_core::find_thread_path_by_name_str; use codex_core::protocol::SessionSource; use codex_protocol::ThreadId; use codex_protocol::models::BaseInstructions; +use codex_state::StateRuntime; +use codex_state::ThreadMetadataBuilder; use pretty_assertions::assert_eq; use tempfile::TempDir; use uuid::Uuid; @@ -52,6 +55,21 @@ fn write_minimal_rollout_with_id(codex_home: &Path, id: Uuid) -> PathBuf { write_minimal_rollout_with_id_in_subdir(codex_home, "sessions", id) } +async fn upsert_thread_metadata(codex_home: &Path, thread_id: ThreadId, rollout_path: PathBuf) { + let runtime = StateRuntime::init(codex_home.to_path_buf(), "test-provider".to_string(), None) + .await + .unwrap(); + let mut builder = ThreadMetadataBuilder::new( + thread_id, + rollout_path, + Utc::now(), + SessionSource::default(), + ); + builder.cwd = codex_home.to_path_buf(); + let metadata = builder.build("test-provider"); + runtime.upsert_thread(&metadata).await.unwrap(); +} + #[tokio::test] async fn find_locates_rollout_file_by_id() { let home = TempDir::new().unwrap(); @@ -81,6 +99,43 @@ async fn find_handles_gitignore_covering_codex_home_directory() { assert_eq!(found, Some(expected)); } +#[tokio::test] +async fn find_prefers_sqlite_path_by_id() { + let home = TempDir::new().unwrap(); + let id = Uuid::new_v4(); + let thread_id = ThreadId::from_string(&id.to_string()).unwrap(); + let db_path = home + .path() + .join("sessions/2030/12/30/rollout-2030-12-30T00-00-00-db.jsonl"); + write_minimal_rollout_with_id(home.path(), id); + upsert_thread_metadata(home.path(), thread_id, db_path.clone()).await; + + let found = find_thread_path_by_id_str(home.path(), &id.to_string()) + .await + .unwrap(); + + assert_eq!(found, Some(db_path)); +} + +#[tokio::test] +async fn find_falls_back_to_filesystem_when_sqlite_has_no_match() { + let home = TempDir::new().unwrap(); + let id = Uuid::new_v4(); + let expected = write_minimal_rollout_with_id(home.path(), id); + let unrelated_id = Uuid::new_v4(); + let unrelated_thread_id = ThreadId::from_string(&unrelated_id.to_string()).unwrap(); + let unrelated_path = home + .path() + .join("sessions/2030/12/30/rollout-2030-12-30T00-00-00-unrelated.jsonl"); + upsert_thread_metadata(home.path(), unrelated_thread_id, unrelated_path).await; + + let found = find_thread_path_by_id_str(home.path(), &id.to_string()) + .await + .unwrap(); + + assert_eq!(found, Some(expected)); +} + #[tokio::test] async fn find_ignores_granular_gitignore_rules() { let home = TempDir::new().unwrap(); From 9a487f9c18b0fed4c7173d0554a1fbfa13b98753 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 3 Feb 2026 11:26:34 -0800 Subject: [PATCH 111/139] fix: make $PWD/.agents read-only like $PWD/.codex (#10524) In light of https://github.com/openai/codex/pull/10317, because `.agents` can include resources that Codex can run in a privileged way, it should be read-only by default just as `.codex` is. --- codex-rs/protocol/src/protocol.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index bf193cca9c..140d3931e0 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -589,13 +589,18 @@ impl SandboxPolicy { } subpaths.push(top_level_git); } - #[allow(clippy::expect_used)] - let top_level_codex = writable_root - .join(".codex") - .expect(".codex is a valid relative path"); - if top_level_codex.as_path().is_dir() { - subpaths.push(top_level_codex); + + // Make .agents/skills and .codex/config.toml and + // related files read-only to the agent, by default. + for subdir in &[".agents", ".codex"] { + #[allow(clippy::expect_used)] + let top_level_codex = + writable_root.join(subdir).expect("valid relative path"); + if top_level_codex.as_path().is_dir() { + subpaths.push(top_level_codex); + } } + WritableRoot { root: writable_root, read_only_subpaths: subpaths, From 66b196a725f5e36b17e41d00e5851aedbef5012b Mon Sep 17 00:00:00 2001 From: Max Johnson <162359438+maxj-oai@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:31:12 -0800 Subject: [PATCH 112/139] Inject CODEX_THREAD_ID into the terminal environment (#10096) Inject CODEX_THREAD_ID (when applicable) into the terminal environment so that the agent (and skills) can refer to the current thread / session ID. Discussion: https://openai.slack.com/archives/C095U48JNL9/p1769542492067109 --- .../app-server/src/codex_message_processor.rs | 2 +- codex-rs/cli/src/debug_sandbox.rs | 2 +- codex-rs/core/src/exec_env.rs | 100 ++++++++++++++---- codex-rs/core/src/tasks/user_shell.rs | 5 +- codex-rs/core/src/tools/handlers/shell.rs | 37 +++++-- .../core/src/unified_exec/process_manager.rs | 5 +- .../linux-sandbox/tests/suite/landlock.rs | 2 +- 7 files changed, 121 insertions(+), 32 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 9e462ca431..2a1c534056 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1430,7 +1430,7 @@ impl CodexMessageProcessor { } let cwd = params.cwd.unwrap_or_else(|| self.config.cwd.clone()); - let env = create_env(&self.config.shell_environment_policy); + let env = create_env(&self.config.shell_environment_policy, None); let timeout_ms = params .timeout_ms .and_then(|timeout_ms| u64::try_from(timeout_ms).ok()); diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 13cb8cdd86..5b165f9778 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -130,7 +130,7 @@ async fn run_command_under_sandbox( let sandbox_policy_cwd = cwd.clone(); let stdio_policy = StdioPolicy::Inherit; - let env = create_env(&config.shell_environment_policy); + let env = create_env(&config.shell_environment_policy, None); // Special-case Windows sandbox: execute and exit the process to emulate inherited stdio. if let SandboxType::Windows = sandbox_type { diff --git a/codex-rs/core/src/exec_env.rs b/codex-rs/core/src/exec_env.rs index 91bf97ef0c..eabd35b410 100644 --- a/codex-rs/core/src/exec_env.rs +++ b/codex-rs/core/src/exec_env.rs @@ -1,9 +1,12 @@ use crate::config::types::EnvironmentVariablePattern; use crate::config::types::ShellEnvironmentPolicy; use crate::config::types::ShellEnvironmentPolicyInherit; +use codex_protocol::ThreadId; use std::collections::HashMap; use std::collections::HashSet; +pub const CODEX_THREAD_ID_ENV_VAR: &str = "CODEX_THREAD_ID"; + /// Construct an environment map based on the rules in the specified policy. The /// resulting map can be passed directly to `Command::envs()` after calling /// `env_clear()` to ensure no unintended variables are leaked to the spawned @@ -11,11 +14,21 @@ use std::collections::HashSet; /// /// The derivation follows the algorithm documented in the struct-level comment /// for [`ShellEnvironmentPolicy`]. -pub fn create_env(policy: &ShellEnvironmentPolicy) -> HashMap { - populate_env(std::env::vars(), policy) +/// +/// `CODEX_THREAD_ID` is injected when a thread id is provided, even when +/// `include_only` is set. +pub fn create_env( + policy: &ShellEnvironmentPolicy, + thread_id: Option, +) -> HashMap { + populate_env(std::env::vars(), policy, thread_id) } -fn populate_env(vars: I, policy: &ShellEnvironmentPolicy) -> HashMap +fn populate_env( + vars: I, + policy: &ShellEnvironmentPolicy, + thread_id: Option, +) -> HashMap where I: IntoIterator, { @@ -72,6 +85,11 @@ where env_map.retain(|k, _| matches_any(k, &policy.include_only)); } + // Step 6 – Populate the thread ID environment variable when provided. + if let Some(thread_id) = thread_id { + env_map.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); + } + env_map } @@ -98,14 +116,16 @@ mod tests { ]); let policy = ShellEnvironmentPolicy::default(); // inherit All, default excludes ignored - let result = populate_env(vars, &policy); + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); - let expected: HashMap = hashmap! { + let mut expected: HashMap = hashmap! { "PATH".to_string() => "/usr/bin".to_string(), "HOME".to_string() => "/home/user".to_string(), "API_KEY".to_string() => "secret".to_string(), "SECRET_TOKEN".to_string() => "t".to_string(), }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); assert_eq!(result, expected); } @@ -123,12 +143,14 @@ mod tests { ignore_default_excludes: false, // apply KEY/SECRET/TOKEN filter ..Default::default() }; - let result = populate_env(vars, &policy); + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); - let expected: HashMap = hashmap! { + let mut expected: HashMap = hashmap! { "PATH".to_string() => "/usr/bin".to_string(), "HOME".to_string() => "/home/user".to_string(), }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); assert_eq!(result, expected); } @@ -144,11 +166,13 @@ mod tests { ..Default::default() }; - let result = populate_env(vars, &policy); + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); - let expected: HashMap = hashmap! { + let mut expected: HashMap = hashmap! { "PATH".to_string() => "/usr/bin".to_string(), }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); assert_eq!(result, expected); } @@ -163,11 +187,41 @@ mod tests { }; policy.r#set.insert("NEW_VAR".to_string(), "42".to_string()); - let result = populate_env(vars, &policy); + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); + + let mut expected: HashMap = hashmap! { + "PATH".to_string() => "/usr/bin".to_string(), + "NEW_VAR".to_string() => "42".to_string(), + }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); + + assert_eq!(result, expected); + } + + #[test] + fn populate_env_inserts_thread_id() { + let vars = make_vars(&[("PATH", "/usr/bin")]); + let policy = ShellEnvironmentPolicy::default(); + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); + + let mut expected: HashMap = hashmap! { + "PATH".to_string() => "/usr/bin".to_string(), + }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); + + assert_eq!(result, expected); + } + + #[test] + fn populate_env_omits_thread_id_when_missing() { + let vars = make_vars(&[("PATH", "/usr/bin")]); + let policy = ShellEnvironmentPolicy::default(); + let result = populate_env(vars, &policy, None); let expected: HashMap = hashmap! { "PATH".to_string() => "/usr/bin".to_string(), - "NEW_VAR".to_string() => "42".to_string(), }; assert_eq!(result, expected); @@ -183,8 +237,10 @@ mod tests { ..Default::default() }; - let result = populate_env(vars.clone(), &policy); - let expected: HashMap = vars.into_iter().collect(); + let thread_id = ThreadId::new(); + let result = populate_env(vars.clone(), &policy, Some(thread_id)); + let mut expected: HashMap = vars.into_iter().collect(); + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); assert_eq!(result, expected); } @@ -198,10 +254,12 @@ mod tests { ..Default::default() }; - let result = populate_env(vars, &policy); - let expected: HashMap = hashmap! { + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); + let mut expected: HashMap = hashmap! { "PATH".to_string() => "/usr/bin".to_string(), }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); assert_eq!(result, expected); } @@ -220,11 +278,13 @@ mod tests { ..Default::default() }; - let result = populate_env(vars, &policy); - let expected: HashMap = hashmap! { + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); + let mut expected: HashMap = hashmap! { "Path".to_string() => "C:\\Windows\\System32".to_string(), "TEMP".to_string() => "C:\\Temp".to_string(), }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); assert_eq!(result, expected); } @@ -242,10 +302,12 @@ mod tests { .r#set .insert("ONLY_VAR".to_string(), "yes".to_string()); - let result = populate_env(vars, &policy); - let expected: HashMap = hashmap! { + let thread_id = ThreadId::new(); + let result = populate_env(vars, &policy, Some(thread_id)); + let mut expected: HashMap = hashmap! { "ONLY_VAR".to_string() => "yes".to_string(), }; + expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string()); assert_eq!(result, expected); } } diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index fe3688e198..a3d38afd2c 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -105,7 +105,10 @@ impl SessionTask for UserShellCommandTask { let exec_env = ExecEnv { command: exec_command.clone(), cwd: cwd.clone(), - env: create_env(&turn_context.shell_environment_policy), + env: create_env( + &turn_context.shell_environment_policy, + Some(session.conversation_id), + ), // TODO(zhao-oai): Now that we have ExecExpiration::Cancellation, we // should use that instead of an "arbitrarily large" timeout here. expiration: USER_SHELL_TIMEOUT_MS.into(), diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index b59c0140d1..f62caea556 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use codex_protocol::ThreadId; use codex_protocol::models::ShellCommandToolCallParams; use codex_protocol::models::ShellToolCallParams; use std::sync::Arc; @@ -41,12 +42,16 @@ struct RunExecLikeArgs { } impl ShellHandler { - fn to_exec_params(params: &ShellToolCallParams, turn_context: &TurnContext) -> ExecParams { + fn to_exec_params( + params: &ShellToolCallParams, + turn_context: &TurnContext, + thread_id: ThreadId, + ) -> ExecParams { ExecParams { command: params.command.clone(), cwd: turn_context.resolve_path(params.workdir.clone()), expiration: params.timeout_ms.into(), - env: create_env(&turn_context.shell_environment_policy), + env: create_env(&turn_context.shell_environment_policy, Some(thread_id)), sandbox_permissions: params.sandbox_permissions.unwrap_or_default(), windows_sandbox_level: turn_context.windows_sandbox_level, justification: params.justification.clone(), @@ -65,6 +70,7 @@ impl ShellCommandHandler { params: &ShellCommandToolCallParams, session: &crate::codex::Session, turn_context: &TurnContext, + thread_id: ThreadId, ) -> ExecParams { let shell = session.user_shell(); let command = Self::base_command(shell.as_ref(), ¶ms.command, params.login); @@ -73,7 +79,7 @@ impl ShellCommandHandler { command, cwd: turn_context.resolve_path(params.workdir.clone()), expiration: params.timeout_ms.into(), - env: create_env(&turn_context.shell_environment_policy), + env: create_env(&turn_context.shell_environment_policy, Some(thread_id)), sandbox_permissions: params.sandbox_permissions.unwrap_or_default(), windows_sandbox_level: turn_context.windows_sandbox_level, justification: params.justification.clone(), @@ -121,7 +127,8 @@ impl ToolHandler for ShellHandler { ToolPayload::Function { arguments } => { let params: ShellToolCallParams = parse_arguments(&arguments)?; let prefix_rule = params.prefix_rule.clone(); - let exec_params = Self::to_exec_params(¶ms, turn.as_ref()); + let exec_params = + Self::to_exec_params(¶ms, turn.as_ref(), session.conversation_id); Self::run_exec_like(RunExecLikeArgs { tool_name: tool_name.clone(), exec_params, @@ -135,7 +142,8 @@ impl ToolHandler for ShellHandler { .await } ToolPayload::LocalShell { params } => { - let exec_params = Self::to_exec_params(¶ms, turn.as_ref()); + let exec_params = + Self::to_exec_params(¶ms, turn.as_ref(), session.conversation_id); Self::run_exec_like(RunExecLikeArgs { tool_name: tool_name.clone(), exec_params, @@ -197,7 +205,12 @@ impl ToolHandler for ShellCommandHandler { let params: ShellCommandToolCallParams = parse_arguments(&arguments)?; let prefix_rule = params.prefix_rule.clone(); - let exec_params = Self::to_exec_params(¶ms, session.as_ref(), turn.as_ref()); + let exec_params = Self::to_exec_params( + ¶ms, + session.as_ref(), + turn.as_ref(), + session.conversation_id, + ); ShellHandler::run_exec_like(RunExecLikeArgs { tool_name, exec_params, @@ -403,7 +416,10 @@ mod tests { let expected_command = session.user_shell().derive_exec_args(&command, true); let expected_cwd = turn_context.resolve_path(workdir.clone()); - let expected_env = create_env(&turn_context.shell_environment_policy); + let expected_env = create_env( + &turn_context.shell_environment_policy, + Some(session.conversation_id), + ); let params = ShellCommandToolCallParams { command, @@ -415,7 +431,12 @@ mod tests { justification: justification.clone(), }; - let exec_params = ShellCommandHandler::to_exec_params(¶ms, &session, &turn_context); + let exec_params = ShellCommandHandler::to_exec_params( + ¶ms, + &session, + &turn_context, + session.conversation_id, + ); // ExecParams cannot derive Eq due to the CancellationToken field, so we manually compare the fields. assert_eq!(exec_params.command, expected_command); diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index 5b3e83a5dc..6b44926df4 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -483,7 +483,10 @@ impl UnifiedExecProcessManager { cwd: PathBuf, context: &UnifiedExecContext, ) -> Result { - let env = apply_unified_exec_env(create_env(&context.turn.shell_environment_policy)); + let env = apply_unified_exec_env(create_env( + &context.turn.shell_environment_policy, + Some(context.session.conversation_id), + )); let features = context.session.features(); let mut orchestrator = ToolOrchestrator::new(); let mut runtime = UnifiedExecRuntime::new(self); diff --git a/codex-rs/linux-sandbox/tests/suite/landlock.rs b/codex-rs/linux-sandbox/tests/suite/landlock.rs index 84f89a1678..75551d4d91 100644 --- a/codex-rs/linux-sandbox/tests/suite/landlock.rs +++ b/codex-rs/linux-sandbox/tests/suite/landlock.rs @@ -34,7 +34,7 @@ const NETWORK_TIMEOUT_MS: u64 = 10_000; fn create_env_from_core_vars() -> HashMap { let policy = ShellEnvironmentPolicy::default(); - create_env(&policy) + create_env(&policy, None) } #[expect(clippy::print_stdout)] From 1dcce204fcf86ed60e65eb9f6f08dd7c0ccae4ae Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 3 Feb 2026 11:38:44 -0800 Subject: [PATCH 113/139] Revert "Load untrusted rules" (#10536) Reverts openai/codex#9791 --- codex-rs/core/src/exec_policy.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/codex-rs/core/src/exec_policy.rs b/codex-rs/core/src/exec_policy.rs index a2e1e8d40d..2ae5a08e4d 100644 --- a/codex-rs/core/src/exec_policy.rs +++ b/codex-rs/core/src/exec_policy.rs @@ -248,9 +248,7 @@ pub async fn load_exec_policy(config_stack: &ConfigLayerStack) -> Result anyhow::Result<()> { + async fn ignores_rules_from_untrusted_project_layers() -> anyhow::Result<()> { let project_dir = tempdir()?; let policy_dir = project_dir.path().join(RULES_DIR_NAME); fs::create_dir_all(&policy_dir)?; fs::write( - policy_dir.join("disabled.rules"), + policy_dir.join("untrusted.rules"), r#"prefix_rule(pattern=["ls"], decision="forbidden")"#, )?; @@ -699,7 +697,7 @@ mod tests { dot_codex_folder: project_dot_codex_folder, }, TomlValue::Table(Default::default()), - "trust disabled", + "marked untrusted", )]; let config_stack = ConfigLayerStack::new( layers, @@ -711,16 +709,14 @@ mod tests { assert_eq!( Evaluation { - decision: Decision::Forbidden, - matched_rules: vec![RuleMatch::PrefixRuleMatch { - matched_prefix: vec!["ls".to_string()], - decision: Decision::Forbidden, - justification: None, + decision: Decision::Allow, + matched_rules: vec![RuleMatch::HeuristicsRuleMatch { + command: vec!["ls".to_string()], + decision: Decision::Allow, }], }, policy.check_multiple([vec!["ls".to_string()]].iter(), &|_| Decision::Allow) ); - Ok(()) } From efd96c46c71ae660042032f42b3750e46a7f5308 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Tue, 3 Feb 2026 11:51:37 -0800 Subject: [PATCH 114/139] fix(app-server): fix TS annotations for optional fields on requests (#10412) This updates our generated TypeScript types to be more correct with how the server actually behaves, **specifically for JSON-RPC requests**. Before this PR, we'd generate `field: T | null`. After this PR, we will have `field?: T | null`. The latter matches how the server actually works, in that if an optional field is omitted, the server will treat it as null. This also makes it less annoying in theory for clients to upgrade to newer versions of Codex, since adding a new optional field to a JSON-RPC request should not require a client change. NOTE: This only applies to JSON-RPC requests. All other payloads (i.e. responses, notifications) will return `field: T | null` as usual. --- AGENTS.md | 40 +++++++++ .../schema/typescript/v2/AppsListParams.ts | 4 +- .../v2/ChatgptAuthTokensRefreshParams.ts | 2 +- .../schema/typescript/v2/CommandExecParams.ts | 2 +- .../CommandExecutionRequestApprovalParams.ts | 10 +-- .../typescript/v2/ConfigBatchWriteParams.ts | 2 +- .../schema/typescript/v2/ConfigReadParams.ts | 2 +- .../typescript/v2/ConfigValueWriteParams.ts | 2 +- .../typescript/v2/FeedbackUploadParams.ts | 2 +- .../v2/FileChangeRequestApprovalParams.ts | 4 +- .../v2/ListMcpServerStatusParams.ts | 4 +- .../v2/McpServerOauthLoginParams.ts | 2 +- .../schema/typescript/v2/ModelListParams.ts | 4 +- .../schema/typescript/v2/ReviewStartParams.ts | 2 +- .../schema/typescript/v2/ThreadForkParams.ts | 4 +- .../schema/typescript/v2/ThreadListParams.ts | 12 +-- .../typescript/v2/ThreadLoadedListParams.ts | 4 +- .../typescript/v2/ThreadResumeParams.ts | 6 +- .../schema/typescript/v2/ThreadStartParams.ts | 2 +- .../schema/typescript/v2/TurnStartParams.ts | 18 ++-- codex-rs/app-server-protocol/src/export.rs | 27 ++++-- .../app-server-protocol/src/protocol/v2.rs | 82 +++++++++++++++++-- 22 files changed, 179 insertions(+), 58 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4b6f8992e4..77c48ddba0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,3 +112,43 @@ If you don’t have the tool: let request = mock.single_request(); // assert using request.function_call_output(call_id) or request.json_body() or other helpers. ``` + +## App-server API Development Best Practices + +These guidelines apply to app-server protocol work in `codex-rs`, especially: + +- `app-server-protocol/src/protocol/common.rs` +- `app-server-protocol/src/protocol/v2.rs` +- `app-server/README.md` + +### Core Rules + +- All active API development should happen in app-server v2. Do not add new API surface area to v1. +- Follow payload naming consistently: + `*Params` for request payloads, `*Response` for responses, and `*Notification` for notifications. +- Expose RPC methods as `/` and keep `` singular (for example, `thread/read`, `app/list`). +- Always expose fields as camelCase on the wire with `#[serde(rename_all = "camelCase")]` unless a tagged union or explicit compatibility requirement needs a targeted rename. +- Always set `#[ts(export_to = "v2/")]` on v2 request/response/notification types so generated TypeScript lands in the correct namespace. +- Never use `#[serde(skip_serializing_if = "Option::is_none")]` for v2 API payload fields. + Exception: client->server requests that intentionally have no params may use: + `params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>`. +- For client->server JSON-RPC request payloads (`*Params`) only, every optional field must be annotated with `#[ts(optional = nullable)]`. Do not use `#[ts(optional = nullable)]` outside client->server request payloads (`*Params`). +- For client->server JSON-RPC request payloads only, and you want to express a boolean field where omission means `false`, use `#[serde(default, skip_serializing_if = "std::ops::Not::not")] pub field: bool` over `Option`. +- For new list methods, implement cursor pagination by default: + request fields `pub cursor: Option` and `pub limit: Option`, + response fields `pub data: Vec<...>` and `pub next_cursor: Option`. +- Keep Rust and TS wire renames aligned. If a field or variant uses `#[serde(rename = "...")]`, add matching `#[ts(rename = "...")]`. +- For discriminated unions, use explicit tagging in both serializers: + `#[serde(tag = "type", ...)]` and `#[ts(tag = "type", ...)]`. +- Prefer plain `String` IDs at the API boundary (do UUID parsing/conversion internally if needed). +- Timestamps should be integer Unix seconds (`i64`) and named `*_at` (for example, `created_at`, `updated_at`, `resets_at`). +- For experimental API surface area: + use `#[experimental("method/or/field")]`, derive `ExperimentalApi` when field-level gating is needed, and use `inspect_params: true` in `common.rs` when only some fields of a method are experimental. + +### Development Workflow + +- Update docs/examples when API behavior changes (at minimum `app-server/README.md`). +- Regenerate schema fixtures when API shapes change: + `just write-app-server-schema` + (and `just write-app-server-schema --experimental` when experimental API fixtures are affected). +- Validate with `cargo test -p codex-app-server-protocol`. diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts index fe925998bf..a3e6fbf624 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AppsListParams.ts @@ -6,8 +6,8 @@ export type AppsListParams = { /** * Opaque pagination cursor returned by a previous call. */ -cursor: string | null, +cursor?: string | null, /** * Optional page size; defaults to a reasonable server-side value. */ -limit: number | null, }; +limit?: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts index 75e677f5c6..4393c7f7a6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ChatgptAuthTokensRefreshParams.ts @@ -13,4 +13,4 @@ export type ChatgptAuthTokensRefreshParams = { reason: ChatgptAuthTokensRefreshR * This may be `null` when the prior ID token did not include a workspace * identifier (`chatgpt_account_id`) or when the token could not be parsed. */ -previousAccountId: string | null, }; +previousAccountId?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts index fc9c6a39ce..847e19d693 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecParams.ts @@ -3,4 +3,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { SandboxPolicy } from "./SandboxPolicy"; -export type CommandExecParams = { command: Array, timeoutMs: number | null, cwd: string | null, sandboxPolicy: SandboxPolicy | null, }; +export type CommandExecParams = { command: Array, timeoutMs?: number | null, cwd?: string | null, sandboxPolicy?: SandboxPolicy | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts index 3bad1b467c..12b2521431 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionRequestApprovalParams.ts @@ -8,20 +8,20 @@ export type CommandExecutionRequestApprovalParams = { threadId: string, turnId: /** * Optional explanatory reason (e.g. request for network access). */ -reason: string | null, +reason?: string | null, /** * The command to be executed. */ -command?: string, +command?: string | null, /** * The command's working directory. */ -cwd?: string, +cwd?: string | null, /** * Best-effort parsed command actions for friendly display. */ -commandActions?: Array, +commandActions?: Array | null, /** * Optional proposed execpolicy amendment to allow similar commands without prompting. */ -proposedExecpolicyAmendment: ExecPolicyAmendment | null, }; +proposedExecpolicyAmendment?: ExecPolicyAmendment | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts index c4012fc339..77df84e3b1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigBatchWriteParams.ts @@ -7,4 +7,4 @@ export type ConfigBatchWriteParams = { edits: Array, /** * Path to the config file to write; defaults to the user's `config.toml` when omitted. */ -filePath: string | null, expectedVersion: string | null, }; +filePath?: string | null, expectedVersion?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts index 3cb2394bc5..c5d5bc874c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigReadParams.ts @@ -8,4 +8,4 @@ export type ConfigReadParams = { includeLayers: boolean, * return the effective config as seen from that directory (i.e., including any * project layers between `cwd` and the project/repo root). */ -cwd: string | null, }; +cwd?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts index 2dedfe8d02..9204760f85 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigValueWriteParams.ts @@ -8,4 +8,4 @@ export type ConfigValueWriteParams = { keyPath: string, value: JsonValue, mergeS /** * Path to the config file to write; defaults to the user's `config.toml` when omitted. */ -filePath: string | null, expectedVersion: string | null, }; +filePath?: string | null, expectedVersion?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts index aeccc45773..3066e65406 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type FeedbackUploadParams = { classification: string, reason: string | null, threadId: string | null, includeLogs: boolean, }; +export type FeedbackUploadParams = { classification: string, reason?: string | null, threadId?: string | null, includeLogs: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts index a7dbfbb7c0..a7951b6858 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FileChangeRequestApprovalParams.ts @@ -6,9 +6,9 @@ export type FileChangeRequestApprovalParams = { threadId: string, turnId: string /** * Optional explanatory reason (e.g. request for extra write access). */ -reason: string | null, +reason?: string | null, /** * [UNSTABLE] When set, the agent is asking the user to allow writes under this root * for the remainder of the session (unclear if this is honored today). */ -grantRoot: string | null, }; +grantRoot?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts index 8027087fd8..05c02c19f8 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ListMcpServerStatusParams.ts @@ -6,8 +6,8 @@ export type ListMcpServerStatusParams = { /** * Opaque pagination cursor returned by a previous call. */ -cursor: string | null, +cursor?: string | null, /** * Optional page size; defaults to a server-defined value. */ -limit: number | null, }; +limit?: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts index 0ad8f3948a..a61c304609 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpServerOauthLoginParams.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type McpServerOauthLoginParams = { name: string, scopes?: Array, timeoutSecs?: bigint, }; +export type McpServerOauthLoginParams = { name: string, scopes?: Array | null, timeoutSecs?: bigint | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts index a2b7214b7b..b0bc5326c1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ModelListParams.ts @@ -6,8 +6,8 @@ export type ModelListParams = { /** * Opaque pagination cursor returned by a previous call. */ -cursor: string | null, +cursor?: string | null, /** * Optional page size; defaults to a reasonable server-side value. */ -limit: number | null, }; +limit?: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts index c36533f2ec..363e6dda37 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ReviewStartParams.ts @@ -9,4 +9,4 @@ export type ReviewStartParams = { threadId: string, target: ReviewTarget, * Where to run the review: inline (default) on the current thread or * detached on a new thread (returned in `reviewThreadId`). */ -delivery: ReviewDelivery | null, }; +delivery?: ReviewDelivery | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts index 4ecb1b39df..eae506c2b6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts @@ -19,8 +19,8 @@ export type ThreadForkParams = { threadId: string, * [UNSTABLE] Specify the rollout path to fork from. * If specified, the thread_id param will be ignored. */ -path: string | null, +path?: string | null, /** * Configuration overrides for the forked thread, if any. */ -model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, }; +model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts index 357fdd21cd..c54f323f55 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts @@ -8,27 +8,27 @@ export type ThreadListParams = { /** * Opaque pagination cursor returned by a previous call. */ -cursor: string | null, +cursor?: string | null, /** * Optional page size; defaults to a reasonable server-side value. */ -limit: number | null, +limit?: number | null, /** * Optional sort key; defaults to created_at. */ -sortKey: ThreadSortKey | null, +sortKey?: ThreadSortKey | null, /** * Optional provider filter; when set, only sessions recorded under these * providers are returned. When present but empty, includes all providers. */ -modelProviders: Array | null, +modelProviders?: Array | null, /** * Optional source filter; when set, only sessions from these source kinds * are returned. When omitted or empty, defaults to interactive sources. */ -sourceKinds: Array | null, +sourceKinds?: Array | null, /** * Optional archived filter; when set to true, only archived threads are returned. * If false or null, only non-archived threads are returned. */ -archived: boolean | null, }; +archived?: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts index 5419b00bd1..ef1e0ac085 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadLoadedListParams.ts @@ -6,8 +6,8 @@ export type ThreadLoadedListParams = { /** * Opaque pagination cursor returned by a previous call. */ -cursor: string | null, +cursor?: string | null, /** * Optional page size; defaults to no limit. */ -limit: number | null, }; +limit?: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts index dca685aa47..64d1f77d81 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts @@ -24,13 +24,13 @@ export type ThreadResumeParams = { threadId: string, * If specified, the thread will be resumed with the provided history * instead of loaded from disk. */ -history: Array | null, +history?: Array | null, /** * [UNSTABLE] Specify the rollout path to resume from. * If specified, the thread_id param will be ignored. */ -path: string | null, +path?: string | null, /** * Configuration overrides for the resumed thread, if any. */ -model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, personality: Personality | null, }; +model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts index 3b4c205901..b0f1d1e2e8 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts @@ -6,7 +6,7 @@ import type { JsonValue } from "../serde_json/JsonValue"; import type { AskForApproval } from "./AskForApproval"; import type { SandboxMode } from "./SandboxMode"; -export type ThreadStartParams = {model: string | null, modelProvider: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, personality: Personality | null, ephemeral: boolean | null, /** +export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, /** * If true, opt into emitting raw response items on the event stream. * * This is for internal use only (e.g. Codex Cloud). diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts index 4987c9e5c1..8cf1aaf20e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts @@ -14,37 +14,37 @@ export type TurnStartParams = { threadId: string, input: Array, /** * Override the working directory for this turn and subsequent turns. */ -cwd: string | null, +cwd?: string | null, /** * Override the approval policy for this turn and subsequent turns. */ -approvalPolicy: AskForApproval | null, +approvalPolicy?: AskForApproval | null, /** * Override the sandbox policy for this turn and subsequent turns. */ -sandboxPolicy: SandboxPolicy | null, +sandboxPolicy?: SandboxPolicy | null, /** * Override the model for this turn and subsequent turns. */ -model: string | null, +model?: string | null, /** * Override the reasoning effort for this turn and subsequent turns. */ -effort: ReasoningEffort | null, +effort?: ReasoningEffort | null, /** * Override the reasoning summary for this turn and subsequent turns. */ -summary: ReasoningSummary | null, +summary?: ReasoningSummary | null, /** * Override the personality for this turn and subsequent turns. */ -personality: Personality | null, +personality?: Personality | null, /** * Optional JSON Schema used to constrain the final assistant message for this turn. */ -outputSchema: JsonValue | null, +outputSchema?: JsonValue | null, /** * EXPERIMENTAL - set a pre-set collaboration mode. * Takes precedence over model, reasoning_effort, and developer instructions if set. */ -collaborationMode: CollaborationMode | null, }; +collaborationMode?: CollaborationMode | null, }; diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index 7878f72c04..6de7c114ca 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -1402,8 +1402,8 @@ mod tests { use uuid::Uuid; #[test] - fn generated_ts_has_no_optional_nullable_fields() -> Result<()> { - // Assert that there are no types of the form "?: T | null" in the generated TS files. + fn generated_ts_optional_nullable_fields_only_in_params() -> Result<()> { + // Assert that "?: T | null" only appears in generated *Params types. let output_dir = std::env::temp_dir().join(format!("codex_ts_types_{}", Uuid::now_v7())); fs::create_dir(&output_dir)?; @@ -1463,6 +1463,13 @@ mod tests { } if matches!(path.extension().and_then(|ext| ext.to_str()), Some("ts")) { + // Only allow "?: T | null" in objects representing JSON-RPC requests, + // which we assume are called "*Params". + let allow_optional_nullable = path + .file_stem() + .and_then(|stem| stem.to_str()) + .is_some_and(|stem| stem.ends_with("Params")); + let contents = fs::read_to_string(&path)?; if contents.contains("| undefined") { undefined_offenders.push(path.clone()); @@ -1583,9 +1590,11 @@ mod tests { } // If the last non-whitespace before ':' is '?', then this is an - // optional field with a nullable type (i.e., "?: T | null"), - // which we explicitly disallow. - if field_prefix.chars().rev().find(|c| !c.is_whitespace()) == Some('?') { + // optional field with a nullable type (i.e., "?: T | null"). + // These are only allowed in *Params types. + if field_prefix.chars().rev().find(|c| !c.is_whitespace()) == Some('?') + && !allow_optional_nullable + { let line_number = contents[..abs_idx].chars().filter(|c| *c == '\n').count() + 1; let offending_line_end = contents[line_start_idx..] @@ -1613,12 +1622,12 @@ mod tests { "Generated TypeScript still includes unions with `undefined` in {undefined_offenders:?}" ); - // If this assertion fails, it means a field was generated as - // "?: T | null" — i.e., both optional (undefined) and nullable (null). - // We only want either "?: T" or ": T | null". + // If this assertion fails, it means a field was generated as "?: T | null", + // which is both optional (undefined) and nullable (null), for a type not ending + // in "Params" (which represent JSON-RPC requests). assert!( optional_nullable_offenders.is_empty(), - "Generated TypeScript has optional fields with nullable types (disallowed '?: T | null'), add #[ts(optional)] to fix:\n{optional_nullable_offenders:?}" + "Generated TypeScript has optional nullable fields outside *Params types (disallowed '?: T | null'):\n{optional_nullable_offenders:?}" ); Ok(()) diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 431be82193..7ce8cc2ed3 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -480,6 +480,7 @@ pub struct ConfigReadParams { /// Optional working directory to resolve project config layers. If specified, /// return the effective config as seen from that directory (i.e., including any /// project layers between `cwd` and the project/repo root). + #[ts(optional = nullable)] pub cwd: Option, } @@ -525,7 +526,9 @@ pub struct ConfigValueWriteParams { pub value: JsonValue, pub merge_strategy: MergeStrategy, /// Path to the config file to write; defaults to the user's `config.toml` when omitted. + #[ts(optional = nullable)] pub file_path: Option, + #[ts(optional = nullable)] pub expected_version: Option, } @@ -535,7 +538,9 @@ pub struct ConfigValueWriteParams { pub struct ConfigBatchWriteParams { pub edits: Vec, /// Path to the config file to write; defaults to the user's `config.toml` when omitted. + #[ts(optional = nullable)] pub file_path: Option, + #[ts(optional = nullable)] pub expected_version: Option, } @@ -935,6 +940,7 @@ pub struct ChatgptAuthTokensRefreshParams { /// /// This may be `null` when the prior ID token did not include a workspace /// identifier (`chatgpt_account_id`) or when the token could not be parsed. + #[ts(optional = nullable)] pub previous_account_id: Option, } @@ -979,8 +985,10 @@ pub struct GetAccountResponse { #[ts(export_to = "v2/")] pub struct ModelListParams { /// Opaque pagination cursor returned by a previous call. + #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. + #[ts(optional = nullable)] pub limit: Option, } @@ -1039,8 +1047,10 @@ pub struct CollaborationModeListResponse { #[ts(export_to = "v2/")] pub struct ListMcpServerStatusParams { /// Opaque pagination cursor returned by a previous call. + #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a server-defined value. + #[ts(optional = nullable)] pub limit: Option, } @@ -1070,8 +1080,10 @@ pub struct ListMcpServerStatusResponse { #[ts(export_to = "v2/")] pub struct AppsListParams { /// Opaque pagination cursor returned by a previous call. + #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. + #[ts(optional = nullable)] pub limit: Option, } @@ -1116,10 +1128,10 @@ pub struct McpServerRefreshResponse {} pub struct McpServerOauthLoginParams { pub name: String, #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] + #[ts(optional = nullable)] pub scopes: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] + #[ts(optional = nullable)] pub timeout_secs: Option, } @@ -1135,7 +1147,9 @@ pub struct McpServerOauthLoginResponse { #[ts(export_to = "v2/")] pub struct FeedbackUploadParams { pub classification: String, + #[ts(optional = nullable)] pub reason: Option, + #[ts(optional = nullable)] pub thread_id: Option, pub include_logs: bool, } @@ -1153,8 +1167,11 @@ pub struct FeedbackUploadResponse { pub struct CommandExecParams { pub command: Vec, #[ts(type = "number | null")] + #[ts(optional = nullable)] pub timeout_ms: Option, + #[ts(optional = nullable)] pub cwd: Option, + #[ts(optional = nullable)] pub sandbox_policy: Option, } @@ -1175,21 +1192,33 @@ pub struct CommandExecResponse { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartParams { + #[ts(optional = nullable)] pub model: Option, + #[ts(optional = nullable)] pub model_provider: Option, + #[ts(optional = nullable)] pub cwd: Option, + #[ts(optional = nullable)] pub approval_policy: Option, + #[ts(optional = nullable)] pub sandbox: Option, + #[ts(optional = nullable)] pub config: Option>, + #[ts(optional = nullable)] pub base_instructions: Option, + #[ts(optional = nullable)] pub developer_instructions: Option, + #[ts(optional = nullable)] pub personality: Option, + #[ts(optional = nullable)] pub ephemeral: Option, #[experimental("thread/start.dynamicTools")] + #[ts(optional = nullable)] pub dynamic_tools: Option>, /// Test-only experimental field used to validate experimental gating and /// schema filtering behavior in a stable way. #[experimental("thread/start.mockExperimentalField")] + #[ts(optional = nullable)] pub mock_experimental_field: Option, /// If true, opt into emitting raw response items on the event stream. /// @@ -1204,6 +1233,7 @@ pub struct ThreadStartParams { #[ts(export_to = "v2/")] pub struct MockExperimentalMethodParams { /// Test-only payload field. + #[ts(optional = nullable)] pub value: Option, } @@ -1246,21 +1276,32 @@ pub struct ThreadResumeParams { /// [UNSTABLE] FOR CODEX CLOUD - DO NOT USE. /// If specified, the thread will be resumed with the provided history /// instead of loaded from disk. + #[ts(optional = nullable)] pub history: Option>, /// [UNSTABLE] Specify the rollout path to resume from. /// If specified, the thread_id param will be ignored. + #[ts(optional = nullable)] pub path: Option, /// Configuration overrides for the resumed thread, if any. + #[ts(optional = nullable)] pub model: Option, + #[ts(optional = nullable)] pub model_provider: Option, + #[ts(optional = nullable)] pub cwd: Option, + #[ts(optional = nullable)] pub approval_policy: Option, + #[ts(optional = nullable)] pub sandbox: Option, + #[ts(optional = nullable)] pub config: Option>, + #[ts(optional = nullable)] pub base_instructions: Option, + #[ts(optional = nullable)] pub developer_instructions: Option, + #[ts(optional = nullable)] pub personality: Option, } @@ -1292,16 +1333,25 @@ pub struct ThreadForkParams { /// [UNSTABLE] Specify the rollout path to fork from. /// If specified, the thread_id param will be ignored. + #[ts(optional = nullable)] pub path: Option, /// Configuration overrides for the forked thread, if any. + #[ts(optional = nullable)] pub model: Option, + #[ts(optional = nullable)] pub model_provider: Option, + #[ts(optional = nullable)] pub cwd: Option, + #[ts(optional = nullable)] pub approval_policy: Option, + #[ts(optional = nullable)] pub sandbox: Option, + #[ts(optional = nullable)] pub config: Option>, + #[ts(optional = nullable)] pub base_instructions: Option, + #[ts(optional = nullable)] pub developer_instructions: Option, } @@ -1386,19 +1436,25 @@ pub struct ThreadRollbackResponse { #[ts(export_to = "v2/")] pub struct ThreadListParams { /// Opaque pagination cursor returned by a previous call. + #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. + #[ts(optional = nullable)] pub limit: Option, /// Optional sort key; defaults to created_at. + #[ts(optional = nullable)] pub sort_key: Option, /// Optional provider filter; when set, only sessions recorded under these /// providers are returned. When present but empty, includes all providers. + #[ts(optional = nullable)] pub model_providers: Option>, /// Optional source filter; when set, only sessions from these source kinds /// are returned. When omitted or empty, defaults to interactive sources. + #[ts(optional = nullable)] pub source_kinds: Option>, /// Optional archived filter; when set to true, only archived threads are returned. /// If false or null, only non-archived threads are returned. + #[ts(optional = nullable)] pub archived: Option, } @@ -1443,8 +1499,10 @@ pub struct ThreadListResponse { #[ts(export_to = "v2/")] pub struct ThreadLoadedListParams { /// Opaque pagination cursor returned by a previous call. + #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to no limit. + #[ts(optional = nullable)] pub limit: Option, } @@ -1832,24 +1890,33 @@ pub struct TurnStartParams { pub thread_id: String, pub input: Vec, /// Override the working directory for this turn and subsequent turns. + #[ts(optional = nullable)] pub cwd: Option, /// Override the approval policy for this turn and subsequent turns. + #[ts(optional = nullable)] pub approval_policy: Option, /// Override the sandbox policy for this turn and subsequent turns. + #[ts(optional = nullable)] pub sandbox_policy: Option, /// Override the model for this turn and subsequent turns. + #[ts(optional = nullable)] pub model: Option, /// Override the reasoning effort for this turn and subsequent turns. + #[ts(optional = nullable)] pub effort: Option, /// Override the reasoning summary for this turn and subsequent turns. + #[ts(optional = nullable)] pub summary: Option, /// Override the personality for this turn and subsequent turns. + #[ts(optional = nullable)] pub personality: Option, /// Optional JSON Schema used to constrain the final assistant message for this turn. + #[ts(optional = nullable)] pub output_schema: Option, /// EXPERIMENTAL - set a pre-set collaboration mode. /// Takes precedence over model, reasoning_effort, and developer instructions if set. + #[ts(optional = nullable)] pub collaboration_mode: Option, } @@ -1863,6 +1930,7 @@ pub struct ReviewStartParams { /// Where to run the review: inline (default) on the current thread or /// detached on a new thread (returned in `reviewThreadId`). #[serde(default)] + #[ts(optional = nullable)] pub delivery: Option, } @@ -2644,20 +2712,22 @@ pub struct CommandExecutionRequestApprovalParams { pub turn_id: String, pub item_id: String, /// Optional explanatory reason (e.g. request for network access). + #[ts(optional = nullable)] pub reason: Option, /// The command to be executed. #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] + #[ts(optional = nullable)] pub command: Option, /// The command's working directory. #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] + #[ts(optional = nullable)] pub cwd: Option, /// Best-effort parsed command actions for friendly display. #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] + #[ts(optional = nullable)] pub command_actions: Option>, /// Optional proposed execpolicy amendment to allow similar commands without prompting. + #[ts(optional = nullable)] pub proposed_execpolicy_amendment: Option, } @@ -2676,9 +2746,11 @@ pub struct FileChangeRequestApprovalParams { pub turn_id: String, pub item_id: String, /// Optional explanatory reason (e.g. request for extra write access). + #[ts(optional = nullable)] pub reason: Option, /// [UNSTABLE] When set, the agent is asking the user to allow writes under this root /// for the remainder of the session (unclear if this is honored today). + #[ts(optional = nullable)] pub grant_root: Option, } From d9ad5c3c4959d2cabd57f9aaed75e96bb2c07c91 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Tue, 3 Feb 2026 12:08:17 -0800 Subject: [PATCH 115/139] fix(app-server): fix approval events in review mode (#10416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of our partners flagged that they were seeing the wrong order of events when running `review/start` with command exec approvals: ``` {"method":"item/commandExecution/requestApproval","id":0,"params":{"threadId":"019c0b6b-6a42-7c02-99c4-98c80e88ac27","turnId":"0","itemId":"0","reason":"`/bin/zsh -lc 'git show b7a92b4eacf262c575f26b1e1ed621a357642e55 --stat'` requires approval: Xcode-required approval: Require explicit user confirmation for all commands.","proposedExecpolicyAmendment":null}} {"method":"item/started","params":{"item":{"type":"commandExecution","id":"call_AEjlbHqLYNM7kbU3N6uw1CNi","command":"/bin/zsh -lc 'git show b7a92b4eacf262c575f26b1e1ed621a357642e55 --stat'","cwd":"/Users/devingreen/Desktop/SampleProject","processId":null,"status":"inProgress","commandActions":[{"type":"unknown","command":"git show b7a92b4eacf262c575f26b1e1ed621a357642e55 --stat"}],"aggregatedOutput":null,"exitCode":null,"durationMs":null},"threadId":"019c0b6b-6a42-7c02-99c4-98c80e88ac27","turnId":"0"}} ``` **Key fix**: In the review sub‑agent delegate we were forwarding exec (and patch) approvals using the parent turn id (`parent_ctx.sub_id`) as the approval call_id. That made `item/commandExecution/requestApproval.itemId` differ from the actual `item/started` id. We now forward the sub‑agent’s `call_id` from the approval event instead, so the approval item id matches the commandExecution item id in review flows. Here’s the expected event order for an inline `review/start` that triggers an exec approval after this fix: 1. Response to review/start (JSON‑RPC response) - Includes `turn` (status inProgress) and `review_thread_id` (same as parent thread for inline). 2. `turn/started` notification - turnId is the review turn id (e.g., "0"). 3. `item/started` → EnteredReviewMode - item.id == turnId, marks entry into review mode. 4. `item/started` → commandExecution - item.id == (e.g., "review-call-1"), status: inProgress. 5. `item/commandExecution/requestApproval` request - JSON‑RPC request (not a notification). - params.itemId == and params.turnId == turnId. 6. Client replies to approval request (Approved / Declined / etc). 7. If approved: - Optional `item/commandExecution/outputDelta` notifications. - `item/completed` → commandExecution with status and exitCode. 8. Review finishes: - `item/started` → ExitedReviewMode - `item/completed` → ExitedReviewMode - (Agent message items may also appear, depending on review output.) 9. `turn/completed` notification The key being #4 and #5 are now in the proper order with the correct item id. --- codex-rs/app-server/tests/suite/v2/review.rs | 99 +++++++++++++++++++- codex-rs/core/src/codex_delegate.rs | 33 ++++--- 2 files changed, 119 insertions(+), 13 deletions(-) diff --git a/codex-rs/app-server/tests/suite/v2/review.rs b/codex-rs/app-server/tests/suite/v2/review.rs index 7a626abfef..265db809e6 100644 --- a/codex-rs/app-server/tests/suite/v2/review.rs +++ b/codex-rs/app-server/tests/suite/v2/review.rs @@ -1,6 +1,9 @@ use anyhow::Result; use app_test_support::McpProcess; +use app_test_support::create_final_assistant_message_sse_response; use app_test_support::create_mock_responses_server_repeating_assistant; +use app_test_support::create_mock_responses_server_sequence; +use app_test_support::create_shell_command_sse_response; use app_test_support::to_response; use codex_app_server_protocol::ItemCompletedNotification; use codex_app_server_protocol::ItemStartedNotification; @@ -12,6 +15,7 @@ use codex_app_server_protocol::ReviewDelivery; use codex_app_server_protocol::ReviewStartParams; use codex_app_server_protocol::ReviewStartResponse; use codex_app_server_protocol::ReviewTarget; +use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; @@ -129,6 +133,91 @@ async fn review_start_runs_review_turn_and_emits_code_review_item() -> Result<() Ok(()) } +#[tokio::test] +async fn review_start_exec_approval_item_id_matches_command_execution_item() -> Result<()> { + let responses = vec![ + create_shell_command_sse_response( + vec![ + "git".to_string(), + "rev-parse".to_string(), + "HEAD".to_string(), + ], + None, + Some(5000), + "review-call-1", + )?, + create_final_assistant_message_sse_response("done")?, + ]; + let server = create_mock_responses_server_sequence(responses).await; + + let codex_home = TempDir::new()?; + create_config_toml_with_approval_policy(codex_home.path(), &server.uri(), "untrusted")?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_id = start_default_thread(&mut mcp).await?; + + let review_req = mcp + .send_review_start_request(ReviewStartParams { + thread_id, + delivery: Some(ReviewDelivery::Inline), + target: ReviewTarget::Commit { + sha: "1234567deadbeef".to_string(), + title: Some("Check review approvals".to_string()), + }, + }) + .await?; + let review_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(review_req)), + ) + .await??; + let ReviewStartResponse { turn, .. } = to_response::(review_resp)?; + let turn_id = turn.id.clone(); + + let server_req = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_request_message(), + ) + .await??; + let ServerRequest::CommandExecutionRequestApproval { request_id, params } = server_req else { + panic!("expected CommandExecutionRequestApproval request"); + }; + assert_eq!(params.item_id, "review-call-1"); + assert_eq!(params.turn_id, turn_id); + + let mut command_item_id = None; + for _ in 0..10 { + let item_started: JSONRPCNotification = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("item/started"), + ) + .await??; + let started: ItemStartedNotification = + serde_json::from_value(item_started.params.expect("params must be present"))?; + if let ThreadItem::CommandExecution { id, .. } = started.item { + command_item_id = Some(id); + break; + } + } + let command_item_id = command_item_id.expect("did not observe command execution item"); + assert_eq!(command_item_id, params.item_id); + + mcp.send_response( + request_id, + serde_json::json!({ "decision": codex_core::protocol::ReviewDecision::Approved }), + ) + .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + Ok(()) +} + #[tokio::test] async fn review_start_rejects_empty_base_branch() -> Result<()> { let server = create_mock_responses_server_repeating_assistant("Done").await; @@ -299,13 +388,21 @@ async fn start_default_thread(mcp: &mut McpProcess) -> Result { } fn create_config_toml(codex_home: &std::path::Path, server_uri: &str) -> std::io::Result<()> { + create_config_toml_with_approval_policy(codex_home, server_uri, "never") +} + +fn create_config_toml_with_approval_policy( + codex_home: &std::path::Path, + server_uri: &str, + approval_policy: &str, +) -> std::io::Result<()> { let config_toml = codex_home.join("config.toml"); std::fs::write( config_toml, format!( r#" model = "mock-model" -approval_policy = "never" +approval_policy = "{approval_policy}" sandbox_mode = "read-only" model_provider = "mock_provider" diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 7a611c05e2..6499370fc3 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -312,14 +312,22 @@ async fn handle_exec_approval( event: ExecApprovalRequestEvent, cancel_token: &CancellationToken, ) { + let ExecApprovalRequestEvent { + call_id, + command, + cwd, + reason, + proposed_execpolicy_amendment, + .. + } = event; // Race approval with cancellation and timeout to avoid hangs. let approval_fut = parent_session.request_command_approval( parent_ctx, - parent_ctx.sub_id.clone(), - event.command, - event.cwd, - event.reason, - event.proposed_execpolicy_amendment, + call_id, + command, + cwd, + reason, + proposed_execpolicy_amendment, ); let decision = await_approval_with_cancel( approval_fut, @@ -341,14 +349,15 @@ async fn handle_patch_approval( event: ApplyPatchApprovalRequestEvent, cancel_token: &CancellationToken, ) { + let ApplyPatchApprovalRequestEvent { + call_id, + changes, + reason, + grant_root, + .. + } = event; let decision_rx = parent_session - .request_patch_approval( - parent_ctx, - parent_ctx.sub_id.clone(), - event.changes, - event.reason, - event.grant_root, - ) + .request_patch_approval(parent_ctx, call_id, changes, reason, grant_root) .await; let decision = await_approval_with_cancel( async move { decision_rx.await.unwrap_or_default() }, From 998eb8f32be5ea0f29271d07d2fb812a69c6a3da Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Tue, 3 Feb 2026 12:08:38 -0800 Subject: [PATCH 116/139] Improve Default mode prompt (less confusion with Plan mode) (#10545) ## Summary This PR updates `request_user_input` behavior and Default-mode guidance to match current collaboration-mode semantics and reduce model confusion. ## Why - `request_user_input` should be explicitly documented as **Plan-only**. - Tool description and runtime availability checks should be driven by the **same centralized mode policy**. - Default mode prompt needed stronger execution guidance and explicit instruction that `request_user_input` is unavailable. - Error messages should report the **actual mode name** (not aliases that can read as misleading). ## What changed - Centralized `request_user_input` mode policy in `core` handler logic: - Added a single allowed-modes config (`Plan` only). - Reused that policy for: - runtime rejection messaging - tool description text - Updated tool description to include availability constraint: - `"This tool is only available in Plan mode."` - Updated runtime rejection behavior: - `Default` -> `"request_user_input is unavailable in Default mode"` - `Execute` -> `"request_user_input is unavailable in Execute mode"` - `PairProgramming` -> `"request_user_input is unavailable in Pair Programming mode"` - Strengthened Default collaboration prompt: - Added explicit execution-first behavior - Added assumptions-first guidance - Added explicit `request_user_input` unavailability instruction - Added concise progress-reporting expectations - Simplified formatting implementation: - Inlined allowed-mode name collection into `format_allowed_modes()` - Kept `format_allowed_modes()` output for 3+ modes as CSV style (`modes: a,b,c`) --- codex-rs/core/src/tools/handlers/mod.rs | 1 + .../src/tools/handlers/request_user_input.rs | 111 ++++++++++++++++-- codex-rs/core/src/tools/spec.rs | 5 +- .../templates/collaboration_mode/default.md | 10 +- .../core/tests/suite/request_user_input.rs | 22 ++-- 5 files changed, 130 insertions(+), 19 deletions(-) diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index a00c6eba51..dda4760bd7 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -27,6 +27,7 @@ pub use mcp_resource::McpResourceHandler; pub use plan::PlanHandler; pub use read_file::ReadFileHandler; pub use request_user_input::RequestUserInputHandler; +pub(crate) use request_user_input::request_user_input_tool_description; pub use shell::ShellCommandHandler; pub use shell::ShellHandler; pub use test_sync::TestSyncHandler; diff --git a/codex-rs/core/src/tools/handlers/request_user_input.rs b/codex-rs/core/src/tools/handlers/request_user_input.rs index 0164d97466..6d014755b5 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -10,6 +10,56 @@ use crate::tools::registry::ToolKind; use codex_protocol::config_types::ModeKind; use codex_protocol::request_user_input::RequestUserInputArgs; +const REQUEST_USER_INPUT_ALLOWED_MODES: [ModeKind; 1] = [ModeKind::Plan]; + +fn request_user_input_mode_name(mode: ModeKind) -> &'static str { + match mode { + ModeKind::Plan => "Plan", + ModeKind::Default => "Default", + ModeKind::Execute => "Execute", + ModeKind::PairProgramming => "Pair Programming", + } +} + +fn format_allowed_modes() -> String { + let mut mode_names = Vec::with_capacity(REQUEST_USER_INPUT_ALLOWED_MODES.len()); + for mode in REQUEST_USER_INPUT_ALLOWED_MODES { + let name = request_user_input_mode_name(mode); + if !mode_names.contains(&name) { + mode_names.push(name); + } + } + + match mode_names.as_slice() { + [] => "no modes".to_string(), + [mode] => format!("{mode} mode"), + [first, second] => format!("{first} or {second} mode"), + [..] => format!("modes: {}", mode_names.join(",")), + } +} + +fn request_user_input_is_available_in_mode(mode: ModeKind) -> bool { + REQUEST_USER_INPUT_ALLOWED_MODES.contains(&mode) +} + +pub(crate) fn request_user_input_unavailable_message(mode: ModeKind) -> Option { + if request_user_input_is_available_in_mode(mode) { + None + } else { + let mode_name = request_user_input_mode_name(mode); + Some(format!( + "request_user_input is unavailable in {mode_name} mode" + )) + } +} + +pub(crate) fn request_user_input_tool_description() -> String { + let allowed_modes = format_allowed_modes(); + format!( + "Request user input for one to three short questions and wait for the response. This tool is only available in {allowed_modes}." + ) +} + pub struct RequestUserInputHandler; #[async_trait] @@ -37,14 +87,8 @@ impl ToolHandler for RequestUserInputHandler { }; let mode = session.collaboration_mode().await.mode; - if !matches!(mode, ModeKind::Plan | ModeKind::PairProgramming) { - let mode_name = match mode { - ModeKind::Default | ModeKind::Execute => "Default", - ModeKind::Plan | ModeKind::PairProgramming => unreachable!(), - }; - return Err(FunctionCallError::RespondToModel(format!( - "request_user_input is unavailable in {mode_name} mode" - ))); + if let Some(message) = request_user_input_unavailable_message(mode) { + return Err(FunctionCallError::RespondToModel(message)); } let mut args: RequestUserInputArgs = parse_arguments(&arguments)?; @@ -82,3 +126,54 @@ impl ToolHandler for RequestUserInputHandler { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn request_user_input_mode_availability_is_plan_only() { + assert_eq!( + request_user_input_is_available_in_mode(ModeKind::Plan), + true + ); + assert_eq!( + request_user_input_is_available_in_mode(ModeKind::Default), + false + ); + assert_eq!( + request_user_input_is_available_in_mode(ModeKind::Execute), + false + ); + assert_eq!( + request_user_input_is_available_in_mode(ModeKind::PairProgramming), + false + ); + } + + #[test] + fn request_user_input_unavailable_messages_use_default_name_for_default_modes() { + assert_eq!(request_user_input_unavailable_message(ModeKind::Plan), None); + assert_eq!( + request_user_input_unavailable_message(ModeKind::Default), + Some("request_user_input is unavailable in Default mode".to_string()) + ); + assert_eq!( + request_user_input_unavailable_message(ModeKind::Execute), + Some("request_user_input is unavailable in Execute mode".to_string()) + ); + assert_eq!( + request_user_input_unavailable_message(ModeKind::PairProgramming), + Some("request_user_input is unavailable in Pair Programming mode".to_string()) + ); + } + + #[test] + fn request_user_input_tool_description_mentions_plan_only() { + assert_eq!( + request_user_input_tool_description(), + "Request user input for one to three short questions and wait for the response. This tool is only available in Plan mode.".to_string() + ); + } +} diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index c0fd1d0221..8851a157a2 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -9,6 +9,7 @@ use crate::tools::handlers::apply_patch::create_apply_patch_json_tool; use crate::tools::handlers::collab::DEFAULT_WAIT_TIMEOUT_MS; use crate::tools::handlers::collab::MAX_WAIT_TIMEOUT_MS; use crate::tools::handlers::collab::MIN_WAIT_TIMEOUT_MS; +use crate::tools::handlers::request_user_input_tool_description; use crate::tools::registry::ToolRegistryBuilder; use codex_protocol::config_types::WebSearchMode; use codex_protocol::dynamic_tools::DynamicToolSpec; @@ -623,9 +624,7 @@ fn create_request_user_input_tool() -> ToolSpec { ToolSpec::Function(ResponsesApiTool { name: "request_user_input".to_string(), - description: - "Request user input for one to three short questions and wait for the response." - .to_string(), + description: request_user_input_tool_description(), strict: false, parameters: JsonSchema::Object { properties, diff --git a/codex-rs/core/templates/collaboration_mode/default.md b/codex-rs/core/templates/collaboration_mode/default.md index 90b80453fa..c8154d10d9 100644 --- a/codex-rs/core/templates/collaboration_mode/default.md +++ b/codex-rs/core/templates/collaboration_mode/default.md @@ -1 +1,9 @@ -you are now in default mode. +# Collaboration Mode: Default + +You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active. + +## request_user_input availability + +The `request_user_input` tool is unavailable in Default mode. If you call it while in Default mode, it will return an error. + +If a decision is necessary and cannot be discovered from local context, ask the user directly. However, in Default mode you should strongly prefer executing the user's request rather than stopping to ask questions. diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index b5e0396d8a..6d8b8cb035 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -74,11 +74,6 @@ async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()> request_user_input_round_trip_for_mode(ModeKind::Plan).await } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn request_user_input_round_trip_works_in_pair_mode() -> anyhow::Result<()> { - request_user_input_round_trip_for_mode(ModeKind::PairProgramming).await -} - async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Result<()> { skip_if_no_network!(Ok(())); @@ -216,7 +211,7 @@ where .build(&server) .await?; - let mode_slug = mode_name.to_lowercase(); + let mode_slug = mode_name.to_lowercase().replace(' ', "-"); let call_id = format!("user-input-{mode_slug}-call"); let request_args = json!({ "questions": [{ @@ -283,7 +278,7 @@ where #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn request_user_input_rejected_in_execute_mode_alias() -> anyhow::Result<()> { - assert_request_user_input_rejected("Default", |model| CollaborationMode { + assert_request_user_input_rejected("Execute", |model| CollaborationMode { mode: ModeKind::Execute, settings: Settings { model, @@ -306,3 +301,16 @@ async fn request_user_input_rejected_in_default_mode() -> anyhow::Result<()> { }) .await } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn request_user_input_rejected_in_pair_mode_alias() -> anyhow::Result<()> { + assert_request_user_input_rejected("Pair Programming", |model| CollaborationMode { + mode: ModeKind::PairProgramming, + settings: Settings { + model, + reasoning_effort: None, + developer_instructions: None, + }, + }) + .await +} From 654fcb49621475b4a9c39943c67f71262320b656 Mon Sep 17 00:00:00 2001 From: Matthew Zeng Date: Tue, 3 Feb 2026 12:17:53 -0800 Subject: [PATCH 117/139] [apps] Gateway MCP should be blocking. (#10289) Make Apps Gateway MCP blocking since otherwise app mentions may not work when apps are not loaded. Messages sent before apps become available will be queued. This only affects when `apps` feature is enabled. --- codex-rs/core/src/mcp_connection_manager.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 1a5ec559b8..c5fbb8ec3d 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -463,16 +463,8 @@ impl McpConnectionManager { #[instrument(level = "trace", skip_all)] pub async fn list_all_tools(&self) -> HashMap { let mut tools = HashMap::new(); - for (server_name, managed_client) in &self.clients { - let client = if server_name == CODEX_APPS_MCP_SERVER_NAME { - // Avoid blocking on codex_apps_mcp startup; use tools only when ready. - match managed_client.client.clone().now_or_never() { - Some(Ok(client)) => Some(client), - _ => None, - } - } else { - managed_client.client().await.ok() - }; + for managed_client in self.clients.values() { + let client = managed_client.client().await.ok(); if let Some(client) = client { tools.extend(qualify_tools(filter_tools( client.tools, From aabe0f259caf3e0ce297f2d3a6b1372d7088dc83 Mon Sep 17 00:00:00 2001 From: iceweasel-oai Date: Tue, 3 Feb 2026 12:37:51 -0800 Subject: [PATCH 118/139] implement per-workspace capability SIDs for workspace specific ACLs (#10189) Today, there is a single capability SID that allows the sandbox to write to * workspace (cwd) * tmp directories if enabled * additional writable roots This change splits those up, so that each workspace has its own capability SID, while tmp and additional roots, which are installation-wide, are still governed by the "generic" capability SID This isolates workspaces from each other in terms of sandbox write access. Also allows us to protect /.codex when codex runs in a specific --- codex-rs/windows-sandbox-rs/src/acl.rs | 8 +- codex-rs/windows-sandbox-rs/src/audit.rs | 12 +- codex-rs/windows-sandbox-rs/src/cap.rs | 59 ++++++++- .../src/command_runner_win.rs | 36 ++++-- .../windows-sandbox-rs/src/elevated_impl.rs | 13 +- codex-rs/windows-sandbox-rs/src/lib.rs | 74 +++++++++-- .../src/path_normalization.rs | 28 +++++ .../windows-sandbox-rs/src/setup_main_win.rs | 63 +++++++++- .../src/setup_orchestrator.rs | 12 +- codex-rs/windows-sandbox-rs/src/token.rs | 115 +++++++++--------- .../windows-sandbox-rs/src/workspace_acl.rs | 20 +++ 11 files changed, 336 insertions(+), 104 deletions(-) create mode 100644 codex-rs/windows-sandbox-rs/src/path_normalization.rs create mode 100644 codex-rs/windows-sandbox-rs/src/workspace_acl.rs diff --git a/codex-rs/windows-sandbox-rs/src/acl.rs b/codex-rs/windows-sandbox-rs/src/acl.rs index 7dd37a9452..0018856a44 100644 --- a/codex-rs/windows-sandbox-rs/src/acl.rs +++ b/codex-rs/windows-sandbox-rs/src/acl.rs @@ -233,7 +233,9 @@ pub unsafe fn dacl_has_write_deny_for_sid(p_dacl: *mut ACL, psid: *mut c_void) - | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES - | GENERIC_WRITE_MASK; + | GENERIC_WRITE_MASK + | DELETE + | FILE_DELETE_CHILD; for i in 0..info.AceCount { let mut p_ace: *mut c_void = std::ptr::null_mut(); if GetAce(p_dacl as *const ACL, i, &mut p_ace) == 0 { @@ -477,7 +479,9 @@ pub unsafe fn add_deny_write_ace(path: &Path, psid: *mut c_void) -> Result | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES - | GENERIC_WRITE_MASK; + | GENERIC_WRITE_MASK + | DELETE + | FILE_DELETE_CHILD; explicit.grfAccessMode = DENY_ACCESS; explicit.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; explicit.Trustee = trustee; diff --git a/codex-rs/windows-sandbox-rs/src/audit.rs b/codex-rs/windows-sandbox-rs/src/audit.rs index 2e6059e819..1af9f1b919 100644 --- a/codex-rs/windows-sandbox-rs/src/audit.rs +++ b/codex-rs/windows-sandbox-rs/src/audit.rs @@ -3,6 +3,7 @@ use crate::acl::path_mask_allows; use crate::cap::cap_sid_file; use crate::cap::load_or_create_cap_sids; use crate::logging::{debug_log, log_note}; +use crate::path_normalization::canonical_path_key; use crate::policy::SandboxPolicy; use crate::token::convert_string_sid_to_sid; use crate::token::world_sid; @@ -31,11 +32,6 @@ const SKIP_DIR_SUFFIXES: &[&str] = &[ "/programdata", ]; -pub(crate) fn normalize_path_key(p: &Path) -> String { - let n = dunce::canonicalize(p).unwrap_or_else(|_| p.to_path_buf()); - n.to_string_lossy().replace('\\', "/").to_ascii_lowercase() -} - fn unique_push(set: &mut HashSet, out: &mut Vec, p: PathBuf) { if let Ok(abs) = p.canonicalize() { if set.insert(abs.clone()) { @@ -131,7 +127,7 @@ pub fn audit_everyone_writable( checked += 1; let has = check_world_writable(&p); if has { - let key = normalize_path_key(&p); + let key = canonical_path_key(&p); if seen.insert(key) { flagged.push(p); } @@ -149,7 +145,7 @@ pub fn audit_everyone_writable( checked += 1; let has_root = check_world_writable(&root); if has_root { - let key = normalize_path_key(&root); + let key = canonical_path_key(&root); if seen.insert(key) { flagged.push(root.clone()); } @@ -181,7 +177,7 @@ pub fn audit_everyone_writable( checked += 1; let has_child = check_world_writable(&p); if has_child { - let key = normalize_path_key(&p); + let key = canonical_path_key(&p); if seen.insert(key) { flagged.push(p); } diff --git a/codex-rs/windows-sandbox-rs/src/cap.rs b/codex-rs/windows-sandbox-rs/src/cap.rs index 5100684965..a699d81d20 100644 --- a/codex-rs/windows-sandbox-rs/src/cap.rs +++ b/codex-rs/windows-sandbox-rs/src/cap.rs @@ -5,14 +5,23 @@ use rand::RngCore; use rand::SeedableRng; use serde::Deserialize; use serde::Serialize; +use std::collections::HashMap; use std::fs; use std::path::Path; use std::path::PathBuf; +use crate::path_normalization::canonical_path_key; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct CapSids { pub workspace: String, pub readonly: String, + /// Per-workspace capability SIDs keyed by canonicalized CWD string. + /// + /// This is used to isolate workspaces from other workspace sandbox writes and to + /// apply per-workspace denies (e.g. protect `CWD/.codex`) + /// without permanently affecting other workspaces. + #[serde(default)] + pub workspace_by_cwd: HashMap, } pub fn cap_sid_file(codex_home: &Path) -> PathBuf { @@ -30,8 +39,7 @@ fn make_random_cap_sid_string() -> String { fn persist_caps(path: &Path, caps: &CapSids) -> Result<()> { if let Some(dir) = path.parent() { - fs::create_dir_all(dir) - .with_context(|| format!("create cap sid dir {}", dir.display()))?; + fs::create_dir_all(dir).with_context(|| format!("create cap sid dir {}", dir.display()))?; } let json = serde_json::to_string(caps)?; fs::write(path, json).with_context(|| format!("write cap sid file {}", path.display()))?; @@ -52,6 +60,7 @@ pub fn load_or_create_cap_sids(codex_home: &Path) -> Result { let caps = CapSids { workspace: t.to_string(), readonly: make_random_cap_sid_string(), + workspace_by_cwd: HashMap::new(), }; persist_caps(&path, &caps)?; return Ok(caps); @@ -60,7 +69,53 @@ pub fn load_or_create_cap_sids(codex_home: &Path) -> Result { let caps = CapSids { workspace: make_random_cap_sid_string(), readonly: make_random_cap_sid_string(), + workspace_by_cwd: HashMap::new(), }; persist_caps(&path, &caps)?; Ok(caps) } + +/// Returns the workspace-specific capability SID for `cwd`, creating and persisting it if missing. +pub fn workspace_cap_sid_for_cwd(codex_home: &Path, cwd: &Path) -> Result { + let path = cap_sid_file(codex_home); + let mut caps = load_or_create_cap_sids(codex_home)?; + let key = canonical_path_key(cwd); + if let Some(sid) = caps.workspace_by_cwd.get(&key) { + return Ok(sid.clone()); + } + let sid = make_random_cap_sid_string(); + caps.workspace_by_cwd.insert(key, sid.clone()); + persist_caps(&path, &caps)?; + Ok(sid) +} + +#[cfg(test)] +mod tests { + use super::load_or_create_cap_sids; + use super::workspace_cap_sid_for_cwd; + use pretty_assertions::assert_eq; + use std::path::PathBuf; + + #[test] + fn equivalent_cwd_spellings_share_workspace_sid_key() { + let temp = tempfile::tempdir().expect("tempdir"); + let codex_home = temp.path().join("codex-home"); + std::fs::create_dir_all(&codex_home).expect("create codex home"); + + let workspace = temp.path().join("WorkspaceRoot"); + std::fs::create_dir_all(&workspace).expect("create workspace root"); + + let canonical = dunce::canonicalize(&workspace).expect("canonical workspace root"); + let alt_spelling = PathBuf::from(canonical.to_string_lossy().replace('\\', "/").to_ascii_uppercase()); + + let first_sid = + workspace_cap_sid_for_cwd(&codex_home, canonical.as_path()).expect("first sid"); + let second_sid = + workspace_cap_sid_for_cwd(&codex_home, alt_spelling.as_path()).expect("second sid"); + + assert_eq!(first_sid, second_sid); + + let caps = load_or_create_cap_sids(&codex_home).expect("load caps"); + assert_eq!(caps.workspace_by_cwd.len(), 1); + } +} diff --git a/codex-rs/windows-sandbox-rs/src/command_runner_win.rs b/codex-rs/windows-sandbox-rs/src/command_runner_win.rs index a016c00c75..9ff61de932 100644 --- a/codex-rs/windows-sandbox-rs/src/command_runner_win.rs +++ b/codex-rs/windows-sandbox-rs/src/command_runner_win.rs @@ -5,8 +5,8 @@ use anyhow::Result; use codex_windows_sandbox::allow_null_device; use codex_windows_sandbox::convert_string_sid_to_sid; use codex_windows_sandbox::create_process_as_user; -use codex_windows_sandbox::create_readonly_token_with_cap_from; -use codex_windows_sandbox::create_workspace_write_token_with_cap_from; +use codex_windows_sandbox::create_readonly_token_with_caps_from; +use codex_windows_sandbox::create_workspace_write_token_with_caps_from; use codex_windows_sandbox::get_current_token_for_restriction; use codex_windows_sandbox::hide_current_user_profile_dir; use codex_windows_sandbox::log_note; @@ -20,7 +20,9 @@ use std::path::Path; use std::path::PathBuf; use windows_sys::Win32::Foundation::CloseHandle; use windows_sys::Win32::Foundation::GetLastError; +use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Storage::FileSystem::CreateFileW; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE; @@ -48,7 +50,7 @@ struct RunnerRequest { codex_home: PathBuf, // Real user's CODEX_HOME for shared data (caps, config). real_codex_home: PathBuf, - cap_sid: String, + cap_sids: Vec, command: Vec, cwd: PathBuf, env_map: HashMap, @@ -112,27 +114,43 @@ pub fn main() -> Result<()> { ); let policy = parse_policy(&req.policy_json_or_preset).context("parse policy_json_or_preset")?; - let psid_cap: *mut c_void = unsafe { convert_string_sid_to_sid(&req.cap_sid).unwrap() }; + let mut cap_psids: Vec<*mut c_void> = Vec::new(); + for sid in &req.cap_sids { + let Some(psid) = (unsafe { convert_string_sid_to_sid(sid) }) else { + anyhow::bail!("ConvertStringSidToSidW failed for capability SID"); + }; + cap_psids.push(psid); + } + if cap_psids.is_empty() { + anyhow::bail!("runner: empty capability SID list"); + } // Create restricted token from current process token. let base = unsafe { get_current_token_for_restriction()? }; - let token_res: Result<(HANDLE, *mut c_void)> = unsafe { + let token_res: Result = unsafe { match &policy { - SandboxPolicy::ReadOnly => create_readonly_token_with_cap_from(base, psid_cap), + SandboxPolicy::ReadOnly => create_readonly_token_with_caps_from(base, &cap_psids), SandboxPolicy::WorkspaceWrite { .. } => { - create_workspace_write_token_with_cap_from(base, psid_cap) + create_workspace_write_token_with_caps_from(base, &cap_psids) } SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { unreachable!() } } }; - let (h_token, psid_to_use) = token_res?; + let h_token = token_res?; unsafe { CloseHandle(base); } unsafe { - allow_null_device(psid_to_use); + for psid in &cap_psids { + allow_null_device(*psid); + } + for psid in cap_psids { + if !psid.is_null() { + LocalFree(psid as HLOCAL); + } + } } // Open named pipes for stdio. diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index 7dfcd6e0ae..ecd5732c09 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs @@ -196,7 +196,7 @@ mod windows_impl { codex_home: PathBuf, // Real user's CODEX_HOME for shared data (caps, config). real_codex_home: PathBuf, - cap_sid: String, + cap_sids: Vec, request_file: Option, command: Vec, cwd: PathBuf, @@ -239,14 +239,17 @@ mod windows_impl { anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing") } let caps = load_or_create_cap_sids(codex_home)?; - let (psid_to_use, cap_sid_str) = match &policy { + let (psid_to_use, cap_sids) = match &policy { SandboxPolicy::ReadOnly => ( unsafe { convert_string_sid_to_sid(&caps.readonly).unwrap() }, - caps.readonly.clone(), + vec![caps.readonly.clone()], ), SandboxPolicy::WorkspaceWrite { .. } => ( unsafe { convert_string_sid_to_sid(&caps.workspace).unwrap() }, - caps.workspace.clone(), + vec![ + caps.workspace.clone(), + crate::cap::workspace_cap_sid_for_cwd(codex_home, cwd)?, + ], ), SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { unreachable!("DangerFullAccess handled above") @@ -294,7 +297,7 @@ mod windows_impl { sandbox_policy_cwd: sandbox_policy_cwd.to_path_buf(), codex_home: sandbox_base.clone(), real_codex_home: codex_home.to_path_buf(), - cap_sid: cap_sid_str.clone(), + cap_sids: cap_sids.clone(), request_file: Some(req_file.clone()), command: command.clone(), cwd: cwd.to_path_buf(), diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index fc062d6fd5..b938a86e6b 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -5,8 +5,21 @@ macro_rules! windows_modules { } windows_modules!( - acl, allow, audit, cap, dpapi, env, hide_users, identity, logging, policy, process, token, - winutil + acl, + allow, + audit, + cap, + dpapi, + env, + hide_users, + identity, + logging, + path_normalization, + policy, + process, + token, + winutil, + workspace_acl ); #[cfg(target_os = "windows")] @@ -19,6 +32,8 @@ mod elevated_impl; #[cfg(target_os = "windows")] mod setup_error; +#[cfg(target_os = "windows")] +pub use acl::add_deny_write_ace; #[cfg(target_os = "windows")] pub use acl::allow_null_device; #[cfg(target_os = "windows")] @@ -36,6 +51,8 @@ pub use audit::apply_world_writable_scan_and_denies; #[cfg(target_os = "windows")] pub use cap::load_or_create_cap_sids; #[cfg(target_os = "windows")] +pub use cap::workspace_cap_sid_for_cwd; +#[cfg(target_os = "windows")] pub use dpapi::protect as dpapi_protect; #[cfg(target_os = "windows")] pub use dpapi::unprotect as dpapi_unprotect; @@ -54,6 +71,8 @@ pub use logging::log_note; #[cfg(target_os = "windows")] pub use logging::LOG_FILE_NAME; #[cfg(target_os = "windows")] +pub use path_normalization::canonicalize_path; +#[cfg(target_os = "windows")] pub use policy::parse_policy; #[cfg(target_os = "windows")] pub use policy::SandboxPolicy; @@ -88,7 +107,9 @@ pub use token::convert_string_sid_to_sid; #[cfg(target_os = "windows")] pub use token::create_readonly_token_with_cap_from; #[cfg(target_os = "windows")] -pub use token::create_workspace_write_token_with_cap_from; +pub use token::create_readonly_token_with_caps_from; +#[cfg(target_os = "windows")] +pub use token::create_workspace_write_token_with_caps_from; #[cfg(target_os = "windows")] pub use token::get_current_token_for_restriction; #[cfg(target_os = "windows")] @@ -99,6 +120,10 @@ pub use windows_impl::CaptureResult; pub use winutil::string_from_sid_bytes; #[cfg(target_os = "windows")] pub use winutil::to_wide; +#[cfg(target_os = "windows")] +pub use workspace_acl::is_command_cwd_root; +#[cfg(target_os = "windows")] +pub use workspace_acl::protect_workspace_codex_dir; #[cfg(not(target_os = "windows"))] pub use stub::apply_world_writable_scan_and_denies; @@ -116,6 +141,7 @@ mod windows_impl { use super::allow::compute_allow_paths; use super::allow::AllowDenyPaths; use super::cap::load_or_create_cap_sids; + use super::cap::workspace_cap_sid_for_cwd; use super::env::apply_no_network_to_env; use super::env::ensure_non_interactive_pager; use super::env::normalize_null_device_env; @@ -123,13 +149,17 @@ mod windows_impl { use super::logging::log_failure; use super::logging::log_start; use super::logging::log_success; + use super::path_normalization::canonicalize_path; use super::policy::parse_policy; use super::policy::SandboxPolicy; use super::process::make_env_block; use super::token::convert_string_sid_to_sid; + use super::token::create_workspace_write_token_with_caps_from; use super::winutil::format_last_error; use super::winutil::quote_windows_arg; use super::winutil::to_wide; + use super::workspace_acl::is_command_cwd_root; + use super::workspace_acl::protect_workspace_codex_dir; use anyhow::Result; use std::collections::HashMap; use std::ffi::c_void; @@ -229,15 +259,25 @@ mod windows_impl { anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing") } let caps = load_or_create_cap_sids(codex_home)?; - let (h_token, psid_to_use): (HANDLE, *mut c_void) = unsafe { + let (h_token, psid_generic, psid_workspace): (HANDLE, *mut c_void, Option<*mut c_void>) = unsafe { match &policy { SandboxPolicy::ReadOnly => { let psid = convert_string_sid_to_sid(&caps.readonly).unwrap(); - super::token::create_readonly_token_with_cap(psid)? + let (h, _) = super::token::create_readonly_token_with_cap(psid)?; + (h, psid, None) } SandboxPolicy::WorkspaceWrite { .. } => { - let psid = convert_string_sid_to_sid(&caps.workspace).unwrap(); - super::token::create_workspace_write_token_with_cap(psid)? + let psid_generic = convert_string_sid_to_sid(&caps.workspace).unwrap(); + let ws_sid = workspace_cap_sid_for_cwd(codex_home, cwd)?; + let psid_workspace = convert_string_sid_to_sid(&ws_sid).unwrap(); + let base = super::token::get_current_token_for_restriction()?; + let h_res = create_workspace_write_token_with_caps_from( + base, + &[psid_generic, psid_workspace], + ); + windows_sys::Win32::Foundation::CloseHandle(base); + let h = h_res?; + (h, psid_generic, Some(psid_workspace)) } SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { unreachable!("DangerFullAccess handled above") @@ -261,29 +301,39 @@ mod windows_impl { let persist_aces = is_workspace_write; let AllowDenyPaths { allow, deny } = compute_allow_paths(&policy, sandbox_policy_cwd, ¤t_dir, &env_map); + let canonical_cwd = canonicalize_path(¤t_dir); let mut guards: Vec<(PathBuf, *mut c_void)> = Vec::new(); unsafe { for p in &allow { - if let Ok(added) = add_allow_ace(p, psid_to_use) { + let psid = if is_workspace_write && is_command_cwd_root(p, &canonical_cwd) { + psid_workspace.unwrap_or(psid_generic) + } else { + psid_generic + }; + if let Ok(added) = add_allow_ace(p, psid) { if added { if persist_aces { if p.is_dir() { // best-effort seeding omitted intentionally } } else { - guards.push((p.clone(), psid_to_use)); + guards.push((p.clone(), psid)); } } } } for p in &deny { - if let Ok(added) = add_deny_write_ace(p, psid_to_use) { + if let Ok(added) = add_deny_write_ace(p, psid_generic) { if added && !persist_aces { - guards.push((p.clone(), psid_to_use)); + guards.push((p.clone(), psid_generic)); } } } - allow_null_device(psid_to_use); + allow_null_device(psid_generic); + if let Some(psid) = psid_workspace { + allow_null_device(psid); + let _ = protect_workspace_codex_dir(¤t_dir, psid); + } } let (stdin_pair, stdout_pair, stderr_pair) = unsafe { setup_stdio_pipes()? }; diff --git a/codex-rs/windows-sandbox-rs/src/path_normalization.rs b/codex-rs/windows-sandbox-rs/src/path_normalization.rs new file mode 100644 index 0000000000..fe6a932306 --- /dev/null +++ b/codex-rs/windows-sandbox-rs/src/path_normalization.rs @@ -0,0 +1,28 @@ +use std::path::Path; +use std::path::PathBuf; + +pub fn canonicalize_path(path: &Path) -> PathBuf { + dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) +} + +pub fn canonical_path_key(path: &Path) -> String { + canonicalize_path(path) + .to_string_lossy() + .replace('\\', "/") + .to_ascii_lowercase() +} + +#[cfg(test)] +mod tests { + use super::canonical_path_key; + use pretty_assertions::assert_eq; + use std::path::Path; + + #[test] + fn canonical_path_key_normalizes_case_and_separators() { + let windows_style = Path::new(r"C:\Users\Dev\Repo"); + let slash_style = Path::new("c:/users/dev/repo"); + + assert_eq!(canonical_path_key(windows_style), canonical_path_key(slash_style)); + } +} diff --git a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs b/codex-rs/windows-sandbox-rs/src/setup_main_win.rs index 6868028782..28c94a769c 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_main_win.rs @@ -6,18 +6,22 @@ use anyhow::Context; use anyhow::Result; use base64::engine::general_purpose::STANDARD as BASE64; use base64::Engine; +use codex_windows_sandbox::canonicalize_path; use codex_windows_sandbox::convert_string_sid_to_sid; use codex_windows_sandbox::ensure_allow_mask_aces_with_inheritance; use codex_windows_sandbox::ensure_allow_write_aces; use codex_windows_sandbox::extract_setup_failure; use codex_windows_sandbox::hide_newly_created_users; +use codex_windows_sandbox::is_command_cwd_root; use codex_windows_sandbox::load_or_create_cap_sids; use codex_windows_sandbox::log_note; use codex_windows_sandbox::path_mask_allows; +use codex_windows_sandbox::protect_workspace_codex_dir; use codex_windows_sandbox::sandbox_dir; use codex_windows_sandbox::sandbox_secrets_dir; use codex_windows_sandbox::string_from_sid_bytes; use codex_windows_sandbox::to_wide; +use codex_windows_sandbox::workspace_cap_sid_for_cwd; use codex_windows_sandbox::write_setup_error_report; use codex_windows_sandbox::SetupErrorCode; use codex_windows_sandbox::SetupErrorReport; @@ -75,6 +79,7 @@ struct Payload { offline_username: String, online_username: String, codex_home: PathBuf, + command_cwd: PathBuf, read_roots: Vec, write_roots: Vec, real_user: String, @@ -560,6 +565,11 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( )) })? }; + let workspace_sid_str = workspace_cap_sid_for_cwd(&payload.codex_home, &payload.command_cwd)?; + let workspace_psid = unsafe { + convert_string_sid_to_sid(&workspace_sid_str) + .ok_or_else(|| anyhow::anyhow!("convert workspace capability SID failed"))? + }; let mut refresh_errors: Vec = Vec::new(); if !refresh_only { let firewall_result = firewall::ensure_offline_outbound_block(&offline_sid_str, log); @@ -609,12 +619,12 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( let cap_sid_str = caps.workspace.clone(); let sandbox_group_sid_str = string_from_sid_bytes(&sandbox_group_sid).map_err(anyhow::Error::msg)?; - let sid_strings = vec![sandbox_group_sid_str, cap_sid_str]; let write_mask = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE | FILE_DELETE_CHILD; let mut grant_tasks: Vec = Vec::new(); let mut seen_write_roots: HashSet = HashSet::new(); + let canonical_command_cwd = canonicalize_path(&payload.command_cwd); for root in &payload.write_roots { if !seen_write_roots.insert(root.clone()) { @@ -628,7 +638,21 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( continue; } let mut need_grant = false; - for (label, psid) in [("sandbox_group", sandbox_group_psid), ("cap", cap_psid)] { + let is_command_cwd = is_command_cwd_root(root, &canonical_command_cwd); + let cap_label = if is_command_cwd { + "workspace_cap" + } else { + "cap" + }; + let cap_psid_for_root = if is_command_cwd { + workspace_psid + } else { + cap_psid + }; + for (label, psid) in [ + ("sandbox_group", sandbox_group_psid), + (cap_label, cap_psid_for_root), + ] { let has = match path_mask_allows(root, &[psid], write_mask, true) { Ok(h) => h, Err(e) => { @@ -667,7 +691,12 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( let (tx, rx) = mpsc::channel::<(PathBuf, Result)>(); std::thread::scope(|scope| { for root in grant_tasks { - let sid_strings = sid_strings.clone(); + let is_command_cwd = is_command_cwd_root(&root, &canonical_command_cwd); + let sid_strings = if is_command_cwd { + vec![sandbox_group_sid_str.clone(), workspace_sid_str.clone()] + } else { + vec![sandbox_group_sid_str.clone(), cap_sid_str.clone()] + }; let tx = tx.clone(); scope.spawn(move || { // Convert SID strings to psids locally in this thread. @@ -758,6 +787,31 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( let _ = std::fs::remove_file(&legacy_users); } } + + // Protect the current workspace's `.codex` directory from tampering (write/delete) by using a + // workspace-specific capability SID. If `.codex` doesn't exist yet, skip it (it will be picked + // up on the next refresh). + match unsafe { protect_workspace_codex_dir(&payload.command_cwd, workspace_psid) } { + Ok(true) => { + let cwd_codex = payload.command_cwd.join(".codex"); + log_line( + log, + &format!( + "applied deny ACE to protect workspace .codex {}", + cwd_codex.display() + ), + )?; + } + Ok(false) => {} + Err(err) => { + let cwd_codex = payload.command_cwd.join(".codex"); + refresh_errors.push(format!("deny ACE failed on {}: {err}", cwd_codex.display())); + log_line( + log, + &format!("deny ACE failed on {}: {err}", cwd_codex.display()), + )?; + } + } unsafe { if !sandbox_group_psid.is_null() { LocalFree(sandbox_group_psid as HLOCAL); @@ -765,6 +819,9 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<( if !cap_psid.is_null() { LocalFree(cap_psid as HLOCAL); } + if !workspace_psid.is_null() { + LocalFree(workspace_psid as HLOCAL); + } } if refresh_only && !refresh_errors.is_empty() { log_line( diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 78e6d566fb..0e43d1ba4b 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -12,6 +12,7 @@ use std::process::Stdio; use crate::allow::compute_allow_paths; use crate::allow::AllowDenyPaths; use crate::logging::log_note; +use crate::path_normalization::canonical_path_key; use crate::policy::SandboxPolicy; use crate::setup_error::clear_setup_error_report; use crate::setup_error::failure; @@ -82,6 +83,7 @@ pub fn run_setup_refresh( offline_username: OFFLINE_USERNAME.to_string(), online_username: ONLINE_USERNAME.to_string(), codex_home: codex_home.to_path_buf(), + command_cwd: command_cwd.to_path_buf(), read_roots, write_roots, real_user: std::env::var("USERNAME").unwrap_or_else(|_| "Administrators".to_string()), @@ -259,6 +261,7 @@ struct ElevationPayload { offline_username: String, online_username: String, codex_home: PathBuf, + command_cwd: PathBuf, read_roots: Vec, write_roots: Vec, real_user: String, @@ -475,6 +478,7 @@ pub fn run_elevated_setup( offline_username: OFFLINE_USERNAME.to_string(), online_username: ONLINE_USERNAME.to_string(), codex_home: codex_home.to_path_buf(), + command_cwd: command_cwd.to_path_buf(), read_roots, write_roots, real_user: std::env::var("USERNAME").unwrap_or_else(|_| "Administrators".to_string()), @@ -517,14 +521,14 @@ fn build_payload_roots( fn filter_sensitive_write_roots(mut roots: Vec, codex_home: &Path) -> Vec { // Never grant capability write access to CODEX_HOME or anything under CODEX_HOME/.sandbox. // These locations contain sandbox control/state and must remain tamper-resistant. - let codex_home_key = crate::audit::normalize_path_key(codex_home); - let sbx_dir_key = crate::audit::normalize_path_key(&sandbox_dir(codex_home)); + let codex_home_key = canonical_path_key(codex_home); + let sbx_dir_key = canonical_path_key(&sandbox_dir(codex_home)); let sbx_dir_prefix = format!("{}/", sbx_dir_key.trim_end_matches('/')); - let secrets_dir_key = crate::audit::normalize_path_key(&sandbox_secrets_dir(codex_home)); + let secrets_dir_key = canonical_path_key(&sandbox_secrets_dir(codex_home)); let secrets_dir_prefix = format!("{}/", secrets_dir_key.trim_end_matches('/')); roots.retain(|root| { - let key = crate::audit::normalize_path_key(root); + let key = canonical_path_key(root); key != codex_home_key && key != sbx_dir_key && !key.starts_with(&sbx_dir_prefix) diff --git a/codex-rs/windows-sandbox-rs/src/token.rs b/codex-rs/windows-sandbox-rs/src/token.rs index fc0e840eb5..0cc9f59da4 100644 --- a/codex-rs/windows-sandbox-rs/src/token.rs +++ b/codex-rs/windows-sandbox-rs/src/token.rs @@ -282,17 +282,6 @@ unsafe fn enable_single_privilege(h_token: HANDLE, name: &str) -> Result<()> { Ok(()) } -/// # Safety -/// Caller must close the returned token handle. -pub unsafe fn create_workspace_write_token_with_cap( - psid_capability: *mut c_void, -) -> Result<(HANDLE, *mut c_void)> { - let base = get_current_token_for_restriction()?; - let res = create_workspace_write_token_with_cap_from(base, psid_capability); - CloseHandle(base); - res -} - /// # Safety /// Caller must close the returned token handle. pub unsafe fn create_readonly_token_with_cap( @@ -306,61 +295,63 @@ pub unsafe fn create_readonly_token_with_cap( /// # Safety /// Caller must close the returned token handle; base_token must be a valid primary token. -pub unsafe fn create_workspace_write_token_with_cap_from( - base_token: HANDLE, - psid_capability: *mut c_void, -) -> Result<(HANDLE, *mut c_void)> { - let mut logon_sid_bytes = get_logon_sid_bytes(base_token)?; - let psid_logon = logon_sid_bytes.as_mut_ptr() as *mut c_void; - let mut everyone = world_sid()?; - let psid_everyone = everyone.as_mut_ptr() as *mut c_void; - let mut entries: [SID_AND_ATTRIBUTES; 3] = std::mem::zeroed(); - // Exact set and order: Capability, Logon, Everyone - entries[0].Sid = psid_capability; - entries[0].Attributes = 0; - entries[1].Sid = psid_logon; - entries[1].Attributes = 0; - entries[2].Sid = psid_everyone; - entries[2].Attributes = 0; - let mut new_token: HANDLE = 0; - let flags = DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED; - let ok = CreateRestrictedToken( - base_token, - flags, - 0, - std::ptr::null(), - 0, - std::ptr::null(), - 3, - entries.as_mut_ptr(), - &mut new_token, - ); - if ok == 0 { - return Err(anyhow!("CreateRestrictedToken failed: {}", GetLastError())); - } - set_default_dacl(new_token, &[psid_logon, psid_everyone, psid_capability])?; - enable_single_privilege(new_token, "SeChangeNotifyPrivilege")?; - Ok((new_token, psid_capability)) -} - /// # Safety /// Caller must close the returned token handle; base_token must be a valid primary token. pub unsafe fn create_readonly_token_with_cap_from( base_token: HANDLE, psid_capability: *mut c_void, ) -> Result<(HANDLE, *mut c_void)> { + let new_token = create_token_with_caps_from(base_token, &[psid_capability])?; + Ok((new_token, psid_capability)) +} + +/// Create a restricted token that includes all provided capability SIDs. +/// +/// # Safety +/// Caller must close the returned token handle; base_token must be a valid primary token. +pub unsafe fn create_workspace_write_token_with_caps_from( + base_token: HANDLE, + psid_capabilities: &[*mut c_void], +) -> Result { + create_token_with_caps_from(base_token, psid_capabilities) +} + +/// Create a restricted token that includes all provided capability SIDs. +/// +/// # Safety +/// Caller must close the returned token handle; base_token must be a valid primary token. +pub unsafe fn create_readonly_token_with_caps_from( + base_token: HANDLE, + psid_capabilities: &[*mut c_void], +) -> Result { + create_token_with_caps_from(base_token, psid_capabilities) +} + +unsafe fn create_token_with_caps_from( + base_token: HANDLE, + psid_capabilities: &[*mut c_void], +) -> Result { + if psid_capabilities.is_empty() { + return Err(anyhow!("no capability SIDs provided")); + } let mut logon_sid_bytes = get_logon_sid_bytes(base_token)?; let psid_logon = logon_sid_bytes.as_mut_ptr() as *mut c_void; let mut everyone = world_sid()?; let psid_everyone = everyone.as_mut_ptr() as *mut c_void; - let mut entries: [SID_AND_ATTRIBUTES; 3] = std::mem::zeroed(); - // Exact set and order: Capability, Logon, Everyone - entries[0].Sid = psid_capability; - entries[0].Attributes = 0; - entries[1].Sid = psid_logon; - entries[1].Attributes = 0; - entries[2].Sid = psid_everyone; - entries[2].Attributes = 0; + + // Exact order: Capabilities..., Logon, Everyone + let mut entries: Vec = + vec![std::mem::zeroed(); psid_capabilities.len() + 2]; + for (i, psid) in psid_capabilities.iter().enumerate() { + entries[i].Sid = *psid; + entries[i].Attributes = 0; + } + let logon_idx = psid_capabilities.len(); + entries[logon_idx].Sid = psid_logon; + entries[logon_idx].Attributes = 0; + entries[logon_idx + 1].Sid = psid_everyone; + entries[logon_idx + 1].Attributes = 0; + let mut new_token: HANDLE = 0; let flags = DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED; let ok = CreateRestrictedToken( @@ -370,14 +361,20 @@ pub unsafe fn create_readonly_token_with_cap_from( std::ptr::null(), 0, std::ptr::null(), - 3, + entries.len() as u32, entries.as_mut_ptr(), &mut new_token, ); if ok == 0 { return Err(anyhow!("CreateRestrictedToken failed: {}", GetLastError())); } - set_default_dacl(new_token, &[psid_logon, psid_everyone, psid_capability])?; + + let mut dacl_sids: Vec<*mut c_void> = Vec::with_capacity(psid_capabilities.len() + 2); + dacl_sids.push(psid_logon); + dacl_sids.push(psid_everyone); + dacl_sids.extend_from_slice(psid_capabilities); + set_default_dacl(new_token, &dacl_sids)?; + enable_single_privilege(new_token, "SeChangeNotifyPrivilege")?; - Ok((new_token, psid_capability)) + Ok(new_token) } diff --git a/codex-rs/windows-sandbox-rs/src/workspace_acl.rs b/codex-rs/windows-sandbox-rs/src/workspace_acl.rs new file mode 100644 index 0000000000..b34a7191ad --- /dev/null +++ b/codex-rs/windows-sandbox-rs/src/workspace_acl.rs @@ -0,0 +1,20 @@ +use crate::acl::add_deny_write_ace; +use crate::path_normalization::canonicalize_path; +use anyhow::Result; +use std::ffi::c_void; +use std::path::Path; + +pub fn is_command_cwd_root(root: &Path, canonical_command_cwd: &Path) -> bool { + canonicalize_path(root) == canonical_command_cwd +} + +/// # Safety +/// Caller must ensure `psid` is a valid SID pointer. +pub unsafe fn protect_workspace_codex_dir(cwd: &Path, psid: *mut c_void) -> Result { + let cwd_codex = cwd.join(".codex"); + if cwd_codex.is_dir() { + add_deny_write_ace(&cwd_codex, psid) + } else { + Ok(false) + } +} From 477379b83c1af0bcb97a93c43e87af1a59ec7954 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 3 Feb 2026 12:46:52 -0800 Subject: [PATCH 119/139] Updated bug templates and added a new one for app (#10548) --- .github/ISSUE_TEMPLATE/1-codex-app.yml | 47 +++++++++++++++++++ ...-vs-code-extension.yml => 2-extension.yml} | 13 +++-- .../{2-bug-report.yml => 3-cli.yml} | 21 ++++----- .github/ISSUE_TEMPLATE/4-bug-report.yml | 37 +++++++++++++++ ...ture-request.yml => 5-feature-request.yml} | 2 +- .../{3-docs-issue.yml => 6-docs-issue.yml} | 0 6 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/1-codex-app.yml rename .github/ISSUE_TEMPLATE/{5-vs-code-extension.yml => 2-extension.yml} (83%) rename .github/ISSUE_TEMPLATE/{2-bug-report.yml => 3-cli.yml} (69%) create mode 100644 .github/ISSUE_TEMPLATE/4-bug-report.yml rename .github/ISSUE_TEMPLATE/{4-feature-request.yml => 5-feature-request.yml} (94%) rename .github/ISSUE_TEMPLATE/{3-docs-issue.yml => 6-docs-issue.yml} (100%) diff --git a/.github/ISSUE_TEMPLATE/1-codex-app.yml b/.github/ISSUE_TEMPLATE/1-codex-app.yml new file mode 100644 index 0000000000..569094907f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-codex-app.yml @@ -0,0 +1,47 @@ +name: 🖥️ Codex App Bug +description: Report an issue with the Codex App +labels: + - app +body: + - type: markdown + attributes: + value: | + Before submitting a new issue, please search for existing issues to see if your issue has already been reported. + If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one. + + - type: input + id: version + attributes: + label: What version of the Codex App are you using (From “About Codex” dialog)? + validations: + required: true + - type: input + id: plan + attributes: + label: What subscription do you have? + validations: + required: true + - type: textarea + id: actual + attributes: + label: What issue are you seeing? + description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. + validations: + required: true + - type: textarea + id: steps + attributes: + label: What steps can reproduce the bug? + description: Explain the bug and provide a code snippet that can reproduce it. Please include session id, token limit usage, context window usage if applicable. + validations: + required: true + - type: textarea + id: expected + attributes: + label: What is the expected behavior? + description: If possible, please provide text instead of a screenshot. + - type: textarea + id: notes + attributes: + label: Additional information + description: Is there anything else you think we should know? diff --git a/.github/ISSUE_TEMPLATE/5-vs-code-extension.yml b/.github/ISSUE_TEMPLATE/2-extension.yml similarity index 83% rename from .github/ISSUE_TEMPLATE/5-vs-code-extension.yml rename to .github/ISSUE_TEMPLATE/2-extension.yml index 52da6a7cad..599bc08b42 100644 --- a/.github/ISSUE_TEMPLATE/5-vs-code-extension.yml +++ b/.github/ISSUE_TEMPLATE/2-extension.yml @@ -1,8 +1,7 @@ -name: 🧑‍💻 VS Code Extension -description: Report an issue with the VS Code extension +name: 🧑‍💻 IDE Extension Bug +description: Report an issue with the IDE extension labels: - extension - - needs triage body: - type: markdown attributes: @@ -13,7 +12,7 @@ body: - type: input id: version attributes: - label: What version of the VS Code extension are you using? + label: What version of the IDE extension are you using? validations: required: true - type: input @@ -34,20 +33,20 @@ body: attributes: label: What platform is your computer? description: | - For MacOS and Linux: copy the output of `uname -mprs` + For macOS and Linux: copy the output of `uname -mprs` For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console - type: textarea id: actual attributes: label: What issue are you seeing? - description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. + description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. validations: required: true - type: textarea id: steps attributes: label: What steps can reproduce the bug? - description: Explain the bug and provide a code snippet that can reproduce it. Please include session id, token limit usage, context window usage if applicable. + description: Explain the bug and provide a code snippet that can reproduce it. validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/2-bug-report.yml b/.github/ISSUE_TEMPLATE/3-cli.yml similarity index 69% rename from .github/ISSUE_TEMPLATE/2-bug-report.yml rename to .github/ISSUE_TEMPLATE/3-cli.yml index 2d5ec2ad3f..03d939103a 100644 --- a/.github/ISSUE_TEMPLATE/2-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/3-cli.yml @@ -1,5 +1,5 @@ -name: 🪲 Bug Report -description: Report an issue that should be fixed +name: 🪲 CLI Bug +description: Report an issue in the Codex CLI labels: - bug - needs triage @@ -7,19 +7,16 @@ body: - type: markdown attributes: value: | - Thank you for submitting a bug report! It helps make Codex better for everyone. - - If you need help or support using Codex, and are not reporting a bug, please post on [codex/discussions](https://github.com/openai/codex/discussions), where you can ask questions or engage with others on ideas for how to improve codex. + Before submitting a new issue, please search for existing issues to see if your issue has already been reported. + If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one. Make sure you are running the [latest](https://npmjs.com/package/@openai/codex) version of Codex CLI. The bug you are experiencing may already have been fixed. - Please try to include as much information as possible. - - type: input id: version attributes: - label: What version of Codex is running? - description: (App) look in "About Codex" dialog; (CLI) use `codex --version`; (IDE Extension) look in extensions panel. + label: What version of Codex CLI is running? + description: use `codex --version` validations: required: true - type: input @@ -32,13 +29,13 @@ body: id: model attributes: label: Which model were you using? - description: Like `gpt-4.1`, `o4-mini`, `o3`, etc. + description: Like `gpt-5.2`, `gpt-5.2-codex`, etc. - type: input id: platform attributes: label: What platform is your computer? description: | - For MacOS and Linux: copy the output of `uname -mprs` + For macOS and Linux: copy the output of `uname -mprs` For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console - type: input id: terminal @@ -58,7 +55,7 @@ body: id: steps attributes: label: What steps can reproduce the bug? - description: Explain the bug and provide a code snippet that can reproduce it. Please include session id, token limit usage, context window usage if applicable. + description: Explain the bug and provide a code snippet that can reproduce it. Please include thread id if applicable. validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/4-bug-report.yml b/.github/ISSUE_TEMPLATE/4-bug-report.yml new file mode 100644 index 0000000000..4de8841460 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-bug-report.yml @@ -0,0 +1,37 @@ +name: 🪲 Other Bug +description: Report an issue in Codex Web, integrations, or other Codex components +labels: + - bug +body: + - type: markdown + attributes: + value: | + Before submitting a new issue, please search for existing issues to see if your issue has already been reported. + If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one. + + If you need help or support using Codex and are not reporting a bug, please post on [codex/discussions](https://github.com/openai/codex/discussions), where you can ask questions or engage with others on ideas for how to improve codex. + + - type: textarea + id: actual + attributes: + label: What issue are you seeing? + description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. + validations: + required: true + - type: textarea + id: steps + attributes: + label: What steps can reproduce the bug? + description: Explain the bug and provide a code snippet that can reproduce it. + validations: + required: true + - type: textarea + id: expected + attributes: + label: What is the expected behavior? + description: If possible, please provide text instead of a screenshot. + - type: textarea + id: notes + attributes: + label: Additional information + description: Is there anything else you think we should know? diff --git a/.github/ISSUE_TEMPLATE/4-feature-request.yml b/.github/ISSUE_TEMPLATE/5-feature-request.yml similarity index 94% rename from .github/ISSUE_TEMPLATE/4-feature-request.yml rename to .github/ISSUE_TEMPLATE/5-feature-request.yml index 780fbfab36..55ff9fbbcd 100644 --- a/.github/ISSUE_TEMPLATE/4-feature-request.yml +++ b/.github/ISSUE_TEMPLATE/5-feature-request.yml @@ -16,7 +16,7 @@ body: id: variant attributes: label: What variant of Codex are you using? - description: (e.g., CLI, App, IDE Extension, Web) + description: (e.g., App, IDE Extension, CLI, Web) validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/3-docs-issue.yml b/.github/ISSUE_TEMPLATE/6-docs-issue.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/3-docs-issue.yml rename to .github/ISSUE_TEMPLATE/6-docs-issue.yml From 8406bd7672f0e6b45d060f59e8094785c8a8e4d6 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Tue, 3 Feb 2026 20:47:34 +0000 Subject: [PATCH 120/139] [codex] Default values from requirements if unset (#10531) If we don't set any explicit values for sandbox or approval policy, let's try to use a requirements-satisfying value. --- codex-rs/core/src/config/mod.rs | 281 ++++++++++++++++++++++++++++++-- 1 file changed, 267 insertions(+), 14 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 3e2ed804af..949c08724f 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1094,7 +1094,11 @@ impl ConfigToml { profile_sandbox_mode: Option, windows_sandbox_level: WindowsSandboxLevel, resolved_cwd: &Path, + sandbox_policy_constraint: Option<&Constrained>, ) -> SandboxPolicyResolution { + let sandbox_mode_was_explicit = sandbox_mode_override.is_some() + || profile_sandbox_mode.is_some() + || self.sandbox_mode.is_some(); let resolved_sandbox_mode = sandbox_mode_override .or(profile_sandbox_mode) .or(self.sandbox_mode) @@ -1128,13 +1132,30 @@ impl ConfigToml { SandboxMode::DangerFullAccess => SandboxPolicy::DangerFullAccess, }; let mut forced_auto_mode_downgraded_on_windows = false; - if cfg!(target_os = "windows") - && matches!(resolved_sandbox_mode, SandboxMode::WorkspaceWrite) - // If the experimental Windows sandbox is enabled, do not force a downgrade. - && windows_sandbox_level == codex_protocol::config_types::WindowsSandboxLevel::Disabled + let mut downgrade_workspace_write_if_unsupported = |policy: &mut SandboxPolicy| { + if cfg!(target_os = "windows") + // If the experimental Windows sandbox is enabled, do not force a downgrade. + && windows_sandbox_level + == codex_protocol::config_types::WindowsSandboxLevel::Disabled + && matches!(&*policy, SandboxPolicy::WorkspaceWrite { .. }) + { + *policy = SandboxPolicy::new_read_only_policy(); + forced_auto_mode_downgraded_on_windows = true; + } + }; + if matches!(resolved_sandbox_mode, SandboxMode::WorkspaceWrite) { + downgrade_workspace_write_if_unsupported(&mut sandbox_policy); + } + if !sandbox_mode_was_explicit + && let Some(constraint) = sandbox_policy_constraint + && let Err(err) = constraint.can_set(&sandbox_policy) { - sandbox_policy = SandboxPolicy::new_read_only_policy(); - forced_auto_mode_downgraded_on_windows = true; + tracing::warn!( + error = %err, + "default sandbox policy is disallowed by requirements; falling back to required default" + ); + sandbox_policy = constraint.get().clone(); + downgrade_workspace_write_if_unsupported(&mut sandbox_policy); } SandboxPolicyResolution { policy: sandbox_policy, @@ -1364,6 +1385,9 @@ impl Config { let active_project = cfg .get_active_project(&resolved_cwd) .unwrap_or(ProjectConfig { trust_level: None }); + let sandbox_mode_was_explicit = sandbox_mode.is_some() + || config_profile.sandbox_mode.is_some() + || cfg.sandbox_mode.is_some(); let windows_sandbox_level = WindowsSandboxLevel::from_features(&features); let SandboxPolicyResolution { @@ -1374,6 +1398,7 @@ impl Config { config_profile.sandbox_mode, windows_sandbox_level, &resolved_cwd, + Some(&requirements.sandbox_policy), ); if let SandboxPolicy::WorkspaceWrite { writable_roots, .. } = &mut sandbox_policy { for path in additional_writable_roots { @@ -1382,7 +1407,10 @@ impl Config { } } } - let approval_policy = approval_policy_override + let approval_policy_was_explicit = approval_policy_override.is_some() + || config_profile.approval_policy.is_some() + || cfg.approval_policy.is_some(); + let mut approval_policy = approval_policy_override .or(config_profile.approval_policy) .or(cfg.approval_policy) .unwrap_or_else(|| { @@ -1394,16 +1422,20 @@ impl Config { AskForApproval::default() } }); + if !approval_policy_was_explicit + && let Err(err) = requirements.approval_policy.can_set(&approval_policy) + { + tracing::warn!( + error = %err, + "default approval policy is disallowed by requirements; falling back to required default" + ); + approval_policy = requirements.approval_policy.value(); + } let web_search_mode = resolve_web_search_mode(&cfg, &config_profile, &features); // TODO(dylan): We should be able to leverage ConfigLayerStack so that // we can reliably check this at every config level. - let did_user_set_custom_approval_policy_or_sandbox_mode = approval_policy_override - .is_some() - || config_profile.approval_policy.is_some() - || cfg.approval_policy.is_some() - || sandbox_mode.is_some() - || config_profile.sandbox_mode.is_some() - || cfg.sandbox_mode.is_some(); + let did_user_set_custom_approval_policy_or_sandbox_mode = + approval_policy_was_explicit || sandbox_mode_was_explicit; let mut model_providers = built_in_model_providers(); // Merge user-defined providers into the built-in list. @@ -1919,6 +1951,7 @@ network_access = false # This should be ignored. None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), + None, ); assert_eq!( resolution, @@ -1943,6 +1976,7 @@ network_access = true # This should be ignored. None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), + None, ); assert_eq!( resolution, @@ -1975,6 +2009,7 @@ exclude_slash_tmp = true None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), + None, ); if cfg!(target_os = "windows") { assert_eq!( @@ -2024,6 +2059,7 @@ trust_level = "trusted" None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), + None, ); if cfg!(target_os = "windows") { assert_eq!( @@ -4331,6 +4367,7 @@ trust_level = "untrusted" None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), + None, ); // Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade) @@ -4351,6 +4388,103 @@ trust_level = "untrusted" Ok(()) } + #[test] + fn derive_sandbox_policy_falls_back_to_constraint_value_for_implicit_defaults() + -> anyhow::Result<()> { + let project_dir = TempDir::new()?; + let project_path = project_dir.path().to_path_buf(); + let project_key = project_path.to_string_lossy().to_string(); + let cfg = ConfigToml { + projects: Some(HashMap::from([( + project_key, + ProjectConfig { + trust_level: Some(TrustLevel::Trusted), + }, + )])), + ..Default::default() + }; + let constrained = Constrained::new(SandboxPolicy::DangerFullAccess, |candidate| { + if matches!(candidate, SandboxPolicy::DangerFullAccess) { + Ok(()) + } else { + Err(ConstraintError::InvalidValue { + field_name: "sandbox_mode", + candidate: format!("{candidate:?}"), + allowed: "[DangerFullAccess]".to_string(), + requirement_source: RequirementSource::Unknown, + }) + } + })?; + + let resolution = cfg.derive_sandbox_policy( + None, + None, + WindowsSandboxLevel::Disabled, + &project_path, + Some(&constrained), + ); + + assert_eq!(resolution.policy, SandboxPolicy::DangerFullAccess); + Ok(()) + } + + #[test] + fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback() + -> anyhow::Result<()> { + let project_dir = TempDir::new()?; + let project_path = project_dir.path().to_path_buf(); + let project_key = project_path.to_string_lossy().to_string(); + let cfg = ConfigToml { + projects: Some(HashMap::from([( + project_key, + ProjectConfig { + trust_level: Some(TrustLevel::Trusted), + }, + )])), + ..Default::default() + }; + let constrained = + Constrained::new(SandboxPolicy::new_workspace_write_policy(), |candidate| { + if matches!(candidate, SandboxPolicy::WorkspaceWrite { .. }) { + Ok(()) + } else { + Err(ConstraintError::InvalidValue { + field_name: "sandbox_mode", + candidate: format!("{candidate:?}"), + allowed: "[WorkspaceWrite]".to_string(), + requirement_source: RequirementSource::Unknown, + }) + } + })?; + + let resolution = cfg.derive_sandbox_policy( + None, + None, + WindowsSandboxLevel::Disabled, + &project_path, + Some(&constrained), + ); + + if cfg!(target_os = "windows") { + assert_eq!( + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::ReadOnly, + forced_auto_mode_downgraded_on_windows: true, + } + ); + } else { + assert_eq!( + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::new_workspace_write_policy(), + forced_auto_mode_downgraded_on_windows: false, + } + ); + } + Ok(()) + } + #[test] fn test_resolve_oss_provider_explicit_override() { let config_toml = ConfigToml::default(); @@ -4506,6 +4640,125 @@ mcp_oauth_callback_port = 5678 Ok(()) } + + #[tokio::test] + async fn requirements_disallowing_default_sandbox_falls_back_to_required_default() + -> std::io::Result<()> { + let codex_home = TempDir::new()?; + + let config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .cloud_requirements(CloudRequirementsLoader::new(async { + Some(crate::config_loader::ConfigRequirementsToml { + allowed_sandbox_modes: Some(vec![ + crate::config_loader::SandboxModeRequirement::ReadOnly, + ]), + ..Default::default() + }) + })) + .build() + .await?; + + assert_eq!(*config.sandbox_policy.get(), SandboxPolicy::ReadOnly); + Ok(()) + } + + #[tokio::test] + async fn explicit_sandbox_mode_still_errors_when_disallowed_by_requirements() + -> std::io::Result<()> { + let codex_home = TempDir::new()?; + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + r#"sandbox_mode = "danger-full-access" +"#, + )?; + + let requirements = crate::config_loader::ConfigRequirementsToml { + allowed_approval_policies: None, + allowed_sandbox_modes: Some(vec![ + crate::config_loader::SandboxModeRequirement::ReadOnly, + ]), + mcp_servers: None, + rules: None, + enforce_residency: None, + }; + + let err = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .cloud_requirements(CloudRequirementsLoader::new( + async move { Some(requirements) }, + )) + .build() + .await + .expect_err("explicit disallowed mode should still fail"); + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); + let message = err.to_string(); + assert!(message.contains("invalid value for `sandbox_mode`")); + assert!(message.contains("set by cloud requirements")); + Ok(()) + } + + #[tokio::test] + async fn requirements_disallowing_default_approval_falls_back_to_required_default() + -> std::io::Result<()> { + let codex_home = TempDir::new()?; + let workspace = TempDir::new()?; + let workspace_key = workspace.path().to_string_lossy().replace('\\', "\\\\"); + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + format!( + r#" +[projects."{workspace_key}"] +trust_level = "untrusted" +"# + ), + )?; + + let config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(workspace.path().to_path_buf())) + .cloud_requirements(CloudRequirementsLoader::new(async { + Some(crate::config_loader::ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::OnRequest]), + ..Default::default() + }) + })) + .build() + .await?; + + assert_eq!(config.approval_policy.value(), AskForApproval::OnRequest); + Ok(()) + } + + #[tokio::test] + async fn explicit_approval_policy_still_errors_when_disallowed_by_requirements() + -> std::io::Result<()> { + let codex_home = TempDir::new()?; + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + r#"approval_policy = "untrusted" +"#, + )?; + + let err = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .cloud_requirements(CloudRequirementsLoader::new(async { + Some(crate::config_loader::ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::OnRequest]), + ..Default::default() + }) + })) + .build() + .await + .expect_err("explicit disallowed approval policy should fail"); + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); + let message = err.to_string(); + assert!(message.contains("invalid value for `approval_policy`")); + assert!(message.contains("set by cloud requirements")); + Ok(()) + } } #[cfg(test)] From c87c271128717d21bdc940a0b4cfa9466335d4b5 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 3 Feb 2026 13:27:33 -0800 Subject: [PATCH 121/139] Fixed icon for CLI bug template (#10552) --- .github/ISSUE_TEMPLATE/3-cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/3-cli.yml b/.github/ISSUE_TEMPLATE/3-cli.yml index 03d939103a..4aff813e5f 100644 --- a/.github/ISSUE_TEMPLATE/3-cli.yml +++ b/.github/ISSUE_TEMPLATE/3-cli.yml @@ -1,4 +1,4 @@ -name: 🪲 CLI Bug +name: 💻 CLI Bug description: Report an issue in the Codex CLI labels: - bug From 08926a3fb7a06ae4ceb587e539756858afbd1982 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 3 Feb 2026 13:38:31 -0800 Subject: [PATCH 122/139] chore(arg0): advisory-lock janitor for codex tmp paths (#10039) ## Description ### What changed - Switch the arg0 helper root from `~/.codex/tmp/path` to `~/.codex/tmp/path2` - Add `Arg0PathEntryGuard` to keep both the `TempDir` and an exclusive `.lock` file alive for the process lifetime - Add a startup janitor that scans `path2` and deletes only directories whose lock can be acquired ### Tests - `cargo clippy -p codex-arg0` - `cargo clippy -p codex-core` - `cargo test -p codex-arg0` - `cargo test -p codex-core` --- codex-rs/arg0/src/lib.rs | 141 ++++++++++++++++++++++++++++++- codex-rs/core/tests/suite/mod.rs | 45 +++++++++- 2 files changed, 180 insertions(+), 6 deletions(-) diff --git a/codex-rs/arg0/src/lib.rs b/codex-rs/arg0/src/lib.rs index bf2f7afb7c..9c455ddbba 100644 --- a/codex-rs/arg0/src/lib.rs +++ b/codex-rs/arg0/src/lib.rs @@ -1,3 +1,4 @@ +use std::fs::File; use std::future::Future; use std::path::Path; use std::path::PathBuf; @@ -10,8 +11,24 @@ use tempfile::TempDir; const LINUX_SANDBOX_ARG0: &str = "codex-linux-sandbox"; const APPLY_PATCH_ARG0: &str = "apply_patch"; const MISSPELLED_APPLY_PATCH_ARG0: &str = "applypatch"; +const LOCK_FILENAME: &str = ".lock"; -pub fn arg0_dispatch() -> Option { +/// Keeps the per-session PATH entry alive and locked for the process lifetime. +pub struct Arg0PathEntryGuard { + _temp_dir: TempDir, + _lock_file: File, +} + +impl Arg0PathEntryGuard { + fn new(temp_dir: TempDir, lock_file: File) -> Self { + Self { + _temp_dir: temp_dir, + _lock_file: lock_file, + } + } +} + +pub fn arg0_dispatch() -> Option { // Determine if we were invoked via the special alias. let mut args = std::env::args_os(); let argv0 = args.next().unwrap_or_default(); @@ -149,7 +166,7 @@ where /// /// IMPORTANT: This function modifies the PATH environment variable, so it MUST /// be called before multiple threads are spawned. -pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result { +pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result { let codex_home = codex_core::config::find_codex_home()?; #[cfg(not(debug_assertions))] { @@ -167,7 +184,7 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result { std::fs::create_dir_all(&codex_home)?; // Use a CODEX_HOME-scoped temp root to avoid cluttering the top-level directory. - let temp_root = codex_home.join("tmp").join("path"); + let temp_root = codex_home.join("tmp").join("arg0"); std::fs::create_dir_all(&temp_root)?; #[cfg(unix)] { @@ -177,11 +194,25 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result { std::fs::set_permissions(&temp_root, std::fs::Permissions::from_mode(0o700))?; } + // Best-effort cleanup of stale per-session dirs. Ignore failures so startup proceeds. + if let Err(err) = janitor_cleanup(&temp_root) { + eprintln!("WARNING: failed to clean up stale arg0 temp dirs: {err}"); + } + let temp_dir = tempfile::Builder::new() .prefix("codex-arg0") .tempdir_in(&temp_root)?; let path = temp_dir.path(); + let lock_path = path.join(LOCK_FILENAME); + let lock_file = File::options() + .read(true) + .write(true) + .create(true) + .truncate(false) + .open(&lock_path)?; + lock_file.try_lock()?; + for filename in &[ APPLY_PATCH_ARG0, MISSPELLED_APPLY_PATCH_ARG0, @@ -231,5 +262,107 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result { std::env::set_var("PATH", updated_path_env_var); } - Ok(temp_dir) + Ok(Arg0PathEntryGuard::new(temp_dir, lock_file)) +} + +fn janitor_cleanup(temp_root: &Path) -> std::io::Result<()> { + let entries = match std::fs::read_dir(temp_root) { + Ok(entries) => entries, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(err) => return Err(err), + }; + + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_dir() { + continue; + } + + // Skip the directory if locking fails or the lock is currently held. + let Some(_lock_file) = try_lock_dir(&path)? else { + continue; + }; + + match std::fs::remove_dir_all(&path) { + Ok(()) => {} + // Expected TOCTOU race: directory can disappear after read_dir/lock checks. + Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue, + Err(err) => return Err(err), + } + } + + Ok(()) +} + +fn try_lock_dir(dir: &Path) -> std::io::Result> { + let lock_path = dir.join(LOCK_FILENAME); + let lock_file = match File::options().read(true).write(true).open(&lock_path) { + Ok(file) => file, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err), + }; + + match lock_file.try_lock() { + Ok(()) => Ok(Some(lock_file)), + Err(std::fs::TryLockError::WouldBlock) => Ok(None), + Err(err) => Err(err.into()), + } +} + +#[cfg(test)] +mod tests { + use super::LOCK_FILENAME; + use super::janitor_cleanup; + use std::fs; + use std::fs::File; + use std::path::Path; + + fn create_lock(dir: &Path) -> std::io::Result { + let lock_path = dir.join(LOCK_FILENAME); + File::options() + .read(true) + .write(true) + .create(true) + .truncate(false) + .open(lock_path) + } + + #[test] + fn janitor_skips_dirs_without_lock_file() -> std::io::Result<()> { + let root = tempfile::tempdir()?; + let dir = root.path().join("no-lock"); + fs::create_dir(&dir)?; + + janitor_cleanup(root.path())?; + + assert!(dir.exists()); + Ok(()) + } + + #[test] + fn janitor_skips_dirs_with_held_lock() -> std::io::Result<()> { + let root = tempfile::tempdir()?; + let dir = root.path().join("locked"); + fs::create_dir(&dir)?; + let lock_file = create_lock(&dir)?; + lock_file.try_lock()?; + + janitor_cleanup(root.path())?; + + assert!(dir.exists()); + Ok(()) + } + + #[test] + fn janitor_removes_dirs_with_unlocked_lock() -> std::io::Result<()> { + let root = tempfile::tempdir()?; + let dir = root.path().join("stale"); + fs::create_dir(&dir)?; + create_lock(&dir)?; + + janitor_cleanup(root.path())?; + + assert!(!dir.exists()); + Ok(()) + } } diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index f34ca09b40..47a0829898 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -1,16 +1,57 @@ // Aggregates all former standalone integration tests as modules. +use std::ffi::OsString; + +use codex_arg0::Arg0PathEntryGuard; use codex_arg0::arg0_dispatch; use ctor::ctor; use tempfile::TempDir; +struct TestCodexAliasesGuard { + _codex_home: TempDir, + _arg0: Arg0PathEntryGuard, + _previous_codex_home: Option, +} + +const CODEX_HOME_ENV_VAR: &str = "CODEX_HOME"; + // This code runs before any other tests are run. // It allows the test binary to behave like codex and dispatch to apply_patch and codex-linux-sandbox // based on the arg0. // NOTE: this doesn't work on ARM #[ctor] -pub static CODEX_ALIASES_TEMP_DIR: TempDir = unsafe { +pub static CODEX_ALIASES_TEMP_DIR: TestCodexAliasesGuard = unsafe { #[allow(clippy::unwrap_used)] - arg0_dispatch().unwrap() + let codex_home = tempfile::Builder::new() + .prefix("codex-core-tests") + .tempdir() + .unwrap(); + let previous_codex_home = std::env::var_os(CODEX_HOME_ENV_VAR); + // arg0_dispatch() creates helper links under CODEX_HOME/tmp. Point it at a + // test-owned temp dir so startup never mutates the developer's real ~/.codex. + // + // Safety: #[ctor] runs before tests start, so no test threads exist yet. + unsafe { + std::env::set_var(CODEX_HOME_ENV_VAR, codex_home.path()); + } + + #[allow(clippy::unwrap_used)] + let arg0 = arg0_dispatch().unwrap(); + // Restore the process environment immediately so later tests observe the + // same CODEX_HOME state they started with. + match previous_codex_home.as_ref() { + Some(value) => unsafe { + std::env::set_var(CODEX_HOME_ENV_VAR, value); + }, + None => unsafe { + std::env::remove_var(CODEX_HOME_ENV_VAR); + }, + } + + TestCodexAliasesGuard { + _codex_home: codex_home, + _arg0: arg0, + _previous_codex_home: previous_codex_home, + } }; #[cfg(not(target_os = "windows"))] From f38d181795bd13a50c0775974f96d9afb3e7276b Mon Sep 17 00:00:00 2001 From: xl-openai Date: Tue, 3 Feb 2026 14:09:37 -0800 Subject: [PATCH 123/139] feat: add APIs to list and download public remote skills (#10448) Add API to list / download from remote public skills --- codex-rs/Cargo.lock | 124 +++++++ codex-rs/Cargo.toml | 1 + .../schema/json/ClientRequest.json | 66 ++++ .../schema/json/EventMsg.json | 125 +++++++ .../schema/json/ServerNotification.json | 72 ++++ .../codex_app_server_protocol.schemas.json | 198 +++++++++++ .../json/v1/ForkConversationResponse.json | 72 ++++ .../json/v1/ResumeConversationResponse.json | 72 ++++ .../v1/SessionConfiguredNotification.json | 72 ++++ .../json/v2/SkillsRemoteReadParams.json | 5 + .../json/v2/SkillsRemoteReadResponse.json | 37 +++ .../json/v2/SkillsRemoteWriteParams.json | 17 + .../json/v2/SkillsRemoteWriteResponse.json | 21 ++ .../schema/typescript/ClientRequest.ts | 4 +- .../schema/typescript/EventMsg.ts | 4 +- .../ListRemoteSkillsResponseEvent.ts | 9 + .../typescript/RemoteSkillDownloadedEvent.ts | 8 + .../schema/typescript/RemoteSkillSummary.ts | 5 + .../schema/typescript/index.ts | 3 + .../typescript/v2/RemoteSkillSummary.ts | 5 + .../typescript/v2/SkillsRemoteReadParams.ts | 5 + .../typescript/v2/SkillsRemoteReadResponse.ts | 6 + .../typescript/v2/SkillsRemoteWriteParams.ts | 5 + .../v2/SkillsRemoteWriteResponse.ts | 5 + .../schema/typescript/v2/index.ts | 5 + .../src/protocol/common.rs | 8 + .../app-server-protocol/src/protocol/v2.rs | 38 +++ codex-rs/app-server/README.md | 2 + .../app-server/src/codex_message_processor.rs | 67 ++++ codex-rs/core/Cargo.toml | 1 + codex-rs/core/src/codex.rs | 90 +++++ codex-rs/core/src/rollout/policy.rs | 2 + codex-rs/core/src/skills/mod.rs | 1 + codex-rs/core/src/skills/remote.rs | 314 ++++++++++++++++++ .../src/event_processor_with_human_output.rs | 2 + codex-rs/mcp-server/src/codex_tool_runner.rs | 2 + codex-rs/protocol/src/protocol.rs | 36 ++ codex-rs/tui/src/chatwidget.rs | 1 + 38 files changed, 1508 insertions(+), 2 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/RemoteSkillSummary.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteResponse.ts create mode 100644 codex-rs/core/src/skills/remote.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 2596361529..3d9daeb7ed 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -398,6 +398,15 @@ dependencies = [ "wiremock", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.6.1" @@ -924,6 +933,25 @@ dependencies = [ "bytes", ] +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -1578,6 +1606,7 @@ dependencies = [ "which", "wildmatch", "wiremock", + "zip", "zstd", ] @@ -2353,6 +2382,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.6.0" @@ -2771,6 +2806,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "deflate64" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" + [[package]] name = "der" version = "0.7.10" @@ -2803,6 +2844,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -4929,6 +4981,27 @@ dependencies = [ "url", ] +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "maplit" version = "1.0.2" @@ -10515,6 +10588,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yansi" version = "1.0.1" @@ -10701,12 +10783,54 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap 2.13.0", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror 2.0.18", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + [[package]] name = "zmij" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.13.3" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 22f53644f8..2c92f0b463 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -249,6 +249,7 @@ walkdir = "2.5.0" webbrowser = "1.0" which = "8" wildmatch = "2.6.1" +zip = "2.4.2" wiremock = "0.6" zeroize = "1.8.2" diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 3461572779..39faeb1378 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2183,6 +2183,24 @@ }, "type": "object" }, + "SkillsRemoteReadParams": { + "type": "object" + }, + "SkillsRemoteWriteParams": { + "properties": { + "hazelnutId": { + "type": "string" + }, + "isPreload": { + "type": "boolean" + } + }, + "required": [ + "hazelnutId", + "isPreload" + ], + "type": "object" + }, "TextElement": { "properties": { "byteRange": { @@ -3312,6 +3330,54 @@ "title": "Skills/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/remote/read" + ], + "title": "Skills/remote/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsRemoteReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/remote/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/remote/write" + ], + "title": "Skills/remote/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsRemoteWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/remote/writeRequest", + "type": "object" + }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index 1274e1cd7f..ab556a77cf 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -2012,6 +2012,59 @@ "title": "ListSkillsResponseEventMsg", "type": "object" }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, { "description": "Notification that skill data may have been updated and clients may want to reload.", "properties": { @@ -3429,6 +3482,25 @@ } ] }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, "RequestId": { "anyOf": [ { @@ -6536,6 +6608,59 @@ "title": "ListSkillsResponseEventMsg", "type": "object" }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, { "description": "Notification that skill data may have been updated and clients may want to reload.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index f89bcf24e4..f6d10c11c2 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2590,6 +2590,59 @@ "title": "ListSkillsResponseEventMsg", "type": "object" }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, { "description": "Notification that skill data may have been updated and clients may want to reload.", "properties": { @@ -4469,6 +4522,25 @@ ], "type": "object" }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index bc72ceb21f..83a7c781b0 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -718,6 +718,54 @@ "title": "Skills/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/remote/read" + ], + "title": "Skills/remote/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/SkillsRemoteReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/remote/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/remote/write" + ], + "title": "Skills/remote/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/SkillsRemoteWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/remote/writeRequest", + "type": "object" + }, { "properties": { "id": { @@ -3852,6 +3900,59 @@ "title": "ListSkillsResponseEventMsg", "type": "object" }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/v2/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, { "description": "Notification that skill data may have been updated and clients may want to reload.", "properties": { @@ -6351,6 +6452,25 @@ } ] }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, "RemoveConversationListenerParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -12382,6 +12502,25 @@ "title": "ReasoningTextDeltaNotification", "type": "object" }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, "ResidencyRequirement": { "enum": [ "us" @@ -13417,6 +13556,65 @@ "title": "SkillsListResponse", "type": "object" }, + "SkillsRemoteReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsRemoteReadParams", + "type": "object" + }, + "SkillsRemoteReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/RemoteSkillSummary" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "SkillsRemoteReadResponse", + "type": "object" + }, + "SkillsRemoteWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "hazelnutId": { + "type": "string" + }, + "isPreload": { + "type": "boolean" + } + }, + "required": [ + "hazelnutId", + "isPreload" + ], + "title": "SkillsRemoteWriteParams", + "type": "object" + }, + "SkillsRemoteWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "path" + ], + "title": "SkillsRemoteWriteResponse", + "type": "object" + }, "SubAgentSource": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json index 9894c47c1e..215c80b28d 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json @@ -2012,6 +2012,59 @@ "title": "ListSkillsResponseEventMsg", "type": "object" }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, { "description": "Notification that skill data may have been updated and clients may want to reload.", "properties": { @@ -3429,6 +3482,25 @@ } ] }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json index fe4037a40f..bd6d80b6ed 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json @@ -2012,6 +2012,59 @@ "title": "ListSkillsResponseEventMsg", "type": "object" }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, { "description": "Notification that skill data may have been updated and clients may want to reload.", "properties": { @@ -3429,6 +3482,25 @@ } ] }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json index 8e4c361979..a61d4141a6 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json @@ -2012,6 +2012,59 @@ "title": "ListSkillsResponseEventMsg", "type": "object" }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, { "description": "Notification that skill data may have been updated and clients may want to reload.", "properties": { @@ -3429,6 +3482,25 @@ } ] }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadParams.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadParams.json new file mode 100644 index 0000000000..ace2fb5ba7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadParams.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsRemoteReadParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadResponse.json new file mode 100644 index 0000000000..a8e19c65bb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteReadResponse.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "SkillsRemoteReadResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteParams.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteParams.json new file mode 100644 index 0000000000..871a5a428a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteParams.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "hazelnutId": { + "type": "string" + }, + "isPreload": { + "type": "boolean" + } + }, + "required": [ + "hazelnutId", + "isPreload" + ], + "title": "SkillsRemoteWriteParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteResponse.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteResponse.json new file mode 100644 index 0000000000..1a9473d054 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsRemoteWriteResponse.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "path" + ], + "title": "SkillsRemoteWriteResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index b65d9ee931..d526e5c5e9 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -36,6 +36,8 @@ import type { ModelListParams } from "./v2/ModelListParams"; import type { ReviewStartParams } from "./v2/ReviewStartParams"; import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams"; import type { SkillsListParams } from "./v2/SkillsListParams"; +import type { SkillsRemoteReadParams } from "./v2/SkillsRemoteReadParams"; +import type { SkillsRemoteWriteParams } from "./v2/SkillsRemoteWriteParams"; import type { ThreadArchiveParams } from "./v2/ThreadArchiveParams"; import type { ThreadForkParams } from "./v2/ThreadForkParams"; import type { ThreadListParams } from "./v2/ThreadListParams"; @@ -52,4 +54,4 @@ import type { TurnStartParams } from "./v2/TurnStartParams"; /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts b/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts index 5f158de2cb..c18088eaaf 100644 --- a/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts +++ b/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts @@ -33,6 +33,7 @@ import type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEven import type { ItemCompletedEvent } from "./ItemCompletedEvent"; import type { ItemStartedEvent } from "./ItemStartedEvent"; import type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; +import type { ListRemoteSkillsResponseEvent } from "./ListRemoteSkillsResponseEvent"; import type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; import type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent"; import type { McpStartupCompleteEvent } from "./McpStartupCompleteEvent"; @@ -45,6 +46,7 @@ import type { PlanDeltaEvent } from "./PlanDeltaEvent"; import type { RawResponseItemEvent } from "./RawResponseItemEvent"; import type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent"; import type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; +import type { RemoteSkillDownloadedEvent } from "./RemoteSkillDownloadedEvent"; import type { RequestUserInputEvent } from "./RequestUserInputEvent"; import type { ReviewRequest } from "./ReviewRequest"; import type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; @@ -70,4 +72,4 @@ import type { WebSearchEndEvent } from "./WebSearchEndEvent"; * Response event from the agent * NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen. */ -export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent; +export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "list_remote_skills_response" } & ListRemoteSkillsResponseEvent | { "type": "remote_skill_downloaded" } & RemoteSkillDownloadedEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts new file mode 100644 index 0000000000..e3b277f4d6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RemoteSkillSummary } from "./RemoteSkillSummary"; + +/** + * Response payload for `Op::ListRemoteSkills`. + */ +export type ListRemoteSkillsResponseEvent = { skills: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts new file mode 100644 index 0000000000..83082f2a57 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Response payload for `Op::DownloadRemoteSkill`. + */ +export type RemoteSkillDownloadedEvent = { id: string, name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts new file mode 100644 index 0000000000..7bf57b3b09 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RemoteSkillSummary = { id: string, name: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 91da0708e1..7d3ecb818e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -98,6 +98,7 @@ export type { ItemStartedEvent } from "./ItemStartedEvent"; export type { ListConversationsParams } from "./ListConversationsParams"; export type { ListConversationsResponse } from "./ListConversationsResponse"; export type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; +export type { ListRemoteSkillsResponseEvent } from "./ListRemoteSkillsResponseEvent"; export type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; export type { LocalShellAction } from "./LocalShellAction"; export type { LocalShellExecAction } from "./LocalShellExecAction"; @@ -140,6 +141,8 @@ export type { ReasoningItemContent } from "./ReasoningItemContent"; export type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary"; export type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; export type { ReasoningSummary } from "./ReasoningSummary"; +export type { RemoteSkillDownloadedEvent } from "./RemoteSkillDownloadedEvent"; +export type { RemoteSkillSummary } from "./RemoteSkillSummary"; export type { RemoveConversationListenerParams } from "./RemoveConversationListenerParams"; export type { RemoveConversationSubscriptionResponse } from "./RemoveConversationSubscriptionResponse"; export type { RequestId } from "./RequestId"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RemoteSkillSummary.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteSkillSummary.ts new file mode 100644 index 0000000000..7bf57b3b09 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteSkillSummary.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RemoteSkillSummary = { id: string, name: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadParams.ts new file mode 100644 index 0000000000..9f91787686 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillsRemoteReadParams = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadResponse.ts new file mode 100644 index 0000000000..c1c7b1cc70 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteReadResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RemoteSkillSummary } from "./RemoteSkillSummary"; + +export type SkillsRemoteReadResponse = { data: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteParams.ts new file mode 100644 index 0000000000..857b609ef1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillsRemoteWriteParams = { hazelnutId: string, isPreload: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteResponse.ts new file mode 100644 index 0000000000..cf1665ab97 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRemoteWriteResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillsRemoteWriteResponse = { id: string, name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 0c50231a80..83357ff5c6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -96,6 +96,7 @@ export type { ReasoningEffortOption } from "./ReasoningEffortOption"; export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPartAddedNotification"; export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification"; export type { ReasoningTextDeltaNotification } from "./ReasoningTextDeltaNotification"; +export type { RemoteSkillSummary } from "./RemoteSkillSummary"; export type { ResidencyRequirement } from "./ResidencyRequirement"; export type { ReviewDelivery } from "./ReviewDelivery"; export type { ReviewStartParams } from "./ReviewStartParams"; @@ -116,6 +117,10 @@ export type { SkillsConfigWriteResponse } from "./SkillsConfigWriteResponse"; export type { SkillsListEntry } from "./SkillsListEntry"; export type { SkillsListParams } from "./SkillsListParams"; export type { SkillsListResponse } from "./SkillsListResponse"; +export type { SkillsRemoteReadParams } from "./SkillsRemoteReadParams"; +export type { SkillsRemoteReadResponse } from "./SkillsRemoteReadResponse"; +export type { SkillsRemoteWriteParams } from "./SkillsRemoteWriteParams"; +export type { SkillsRemoteWriteResponse } from "./SkillsRemoteWriteResponse"; export type { TerminalInteractionNotification } from "./TerminalInteractionNotification"; export type { TextElement } from "./TextElement"; export type { TextPosition } from "./TextPosition"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index d20f23a88a..bb2510684e 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -228,6 +228,14 @@ client_request_definitions! { params: v2::SkillsListParams, response: v2::SkillsListResponse, }, + SkillsRemoteRead => "skills/remote/read" { + params: v2::SkillsRemoteReadParams, + response: v2::SkillsRemoteReadResponse, + }, + SkillsRemoteWrite => "skills/remote/write" { + params: v2::SkillsRemoteWriteParams, + response: v2::SkillsRemoteWriteResponse, + }, AppsList => "app/list" { params: v2::AppsListParams, response: v2::AppsListResponse, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 7ce8cc2ed3..67e7013a31 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1554,6 +1554,44 @@ pub struct SkillsListResponse { pub data: Vec, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillsRemoteReadParams {} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct RemoteSkillSummary { + pub id: String, + pub name: String, + pub description: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillsRemoteReadResponse { + pub data: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillsRemoteWriteParams { + pub hazelnut_id: String, + pub is_preload: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillsRemoteWriteResponse { + pub id: String, + pub name: String, + pub path: PathBuf, +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(rename_all = "snake_case")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 1d5b6d15fd..d6fae48466 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -93,6 +93,8 @@ Example (from OpenAI's official VSCode extension): - `model/list` — list available models (with reasoning effort options). - `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). - `skills/list` — list skills for one or more `cwd` values (optional `forceReload`). +- `skills/remote/read` — list public remote skills (**under development; do not call from production clients yet**). +- `skills/remote/write` — download a public remote skill by `hazelnutId`; `isPreload=true` writes to `.codex/vendor_imports/skills` under `codex_home` (**under development; do not call from production clients yet**). - `app/list` — list available apps. - `skills/config/write` — write user-level skill config by path. - `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes. diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 2a1c534056..866998a4fd 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -97,6 +97,10 @@ use codex_app_server_protocol::SkillsConfigWriteParams; use codex_app_server_protocol::SkillsConfigWriteResponse; use codex_app_server_protocol::SkillsListParams; use codex_app_server_protocol::SkillsListResponse; +use codex_app_server_protocol::SkillsRemoteReadParams; +use codex_app_server_protocol::SkillsRemoteReadResponse; +use codex_app_server_protocol::SkillsRemoteWriteParams; +use codex_app_server_protocol::SkillsRemoteWriteResponse; use codex_app_server_protocol::Thread; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; @@ -176,6 +180,8 @@ use codex_core::read_head_for_summary; use codex_core::read_session_meta_line; use codex_core::rollout_date_parts; use codex_core::sandboxing::SandboxPermissions; +use codex_core::skills::remote::download_remote_skill; +use codex_core::skills::remote::list_remote_skills; use codex_core::state_db::get_state_db; use codex_core::token_data::parse_id_token; use codex_core::windows_sandbox::WindowsSandboxLevelExt; @@ -464,6 +470,12 @@ impl CodexMessageProcessor { ClientRequest::SkillsList { request_id, params } => { self.skills_list(request_id, params).await; } + ClientRequest::SkillsRemoteRead { request_id, params } => { + self.skills_remote_read(request_id, params).await; + } + ClientRequest::SkillsRemoteWrite { request_id, params } => { + self.skills_remote_write(request_id, params).await; + } ClientRequest::AppsList { request_id, params } => { self.apps_list(request_id, params).await; } @@ -4053,6 +4065,61 @@ impl CodexMessageProcessor { .await; } + async fn skills_remote_read(&self, request_id: RequestId, _params: SkillsRemoteReadParams) { + match list_remote_skills(&self.config).await { + Ok(skills) => { + let data = skills + .into_iter() + .map(|skill| codex_app_server_protocol::RemoteSkillSummary { + id: skill.id, + name: skill.name, + description: skill.description, + }) + .collect(); + self.outgoing + .send_response(request_id, SkillsRemoteReadResponse { data }) + .await; + } + Err(err) => { + self.send_internal_error( + request_id, + format!("failed to read remote skills: {err}"), + ) + .await; + } + } + } + + async fn skills_remote_write(&self, request_id: RequestId, params: SkillsRemoteWriteParams) { + let SkillsRemoteWriteParams { + hazelnut_id, + is_preload, + } = params; + let response = download_remote_skill(&self.config, hazelnut_id.as_str(), is_preload).await; + + match response { + Ok(downloaded) => { + self.outgoing + .send_response( + request_id, + SkillsRemoteWriteResponse { + id: downloaded.id, + name: downloaded.name, + path: downloaded.path, + }, + ) + .await; + } + Err(err) => { + self.send_internal_error( + request_id, + format!("failed to download remote skill: {err}"), + ) + .await; + } + } + } + async fn skills_config_write(&self, request_id: RequestId, params: SkillsConfigWriteParams) { let SkillsConfigWriteParams { path, enabled } = params; let edits = vec![ConfigEdit::SetSkillConfig { path, enabled }]; diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 38da38b929..f3ba5fd2cb 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -107,6 +107,7 @@ url = { workspace = true } uuid = { workspace = true, features = ["serde", "v4", "v5"] } which = { workspace = true } wildmatch = { workspace = true } +zip = { workspace = true } [features] deterministic_process_ids = [] diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ba34af5ed8..b7c0404056 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2469,6 +2469,22 @@ async fn submission_loop(sess: Arc, config: Arc, rx_sub: Receiv Op::ListSkills { cwds, force_reload } => { handlers::list_skills(&sess, sub.id.clone(), cwds, force_reload).await; } + Op::ListRemoteSkills => { + handlers::list_remote_skills(&sess, &config, sub.id.clone()).await; + } + Op::DownloadRemoteSkill { + hazelnut_id, + is_preload, + } => { + handlers::download_remote_skill( + &sess, + &config, + sub.id.clone(), + hazelnut_id, + is_preload, + ) + .await; + } Op::Undo => { handlers::undo(&sess, sub.id.clone()).await; } @@ -2535,9 +2551,12 @@ mod handlers { use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ListCustomPromptsResponseEvent; + use codex_protocol::protocol::ListRemoteSkillsResponseEvent; use codex_protocol::protocol::ListSkillsResponseEvent; use codex_protocol::protocol::McpServerRefreshConfig; use codex_protocol::protocol::Op; + use codex_protocol::protocol::RemoteSkillDownloadedEvent; + use codex_protocol::protocol::RemoteSkillSummary; use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::ReviewRequest; use codex_protocol::protocol::SkillsListEntry; @@ -2895,6 +2914,77 @@ mod handlers { sess.send_event_raw(event).await; } + pub async fn list_remote_skills(sess: &Session, config: &Arc, sub_id: String) { + let response = crate::skills::remote::list_remote_skills(config) + .await + .map(|skills| { + skills + .into_iter() + .map(|skill| RemoteSkillSummary { + id: skill.id, + name: skill.name, + description: skill.description, + }) + .collect::>() + }); + + match response { + Ok(skills) => { + let event = Event { + id: sub_id, + msg: EventMsg::ListRemoteSkillsResponse(ListRemoteSkillsResponseEvent { + skills, + }), + }; + sess.send_event_raw(event).await; + } + Err(err) => { + let event = Event { + id: sub_id, + msg: EventMsg::Error(ErrorEvent { + message: format!("failed to list remote skills: {err}"), + codex_error_info: Some(CodexErrorInfo::Other), + }), + }; + sess.send_event_raw(event).await; + } + } + } + + pub async fn download_remote_skill( + sess: &Session, + config: &Arc, + sub_id: String, + hazelnut_id: String, + is_preload: bool, + ) { + match crate::skills::remote::download_remote_skill(config, hazelnut_id.as_str(), is_preload) + .await + { + Ok(result) => { + let event = Event { + id: sub_id, + msg: EventMsg::RemoteSkillDownloaded(RemoteSkillDownloadedEvent { + id: result.id, + name: result.name, + path: result.path, + }), + }; + sess.send_event_raw(event).await; + } + Err(err) => { + let event = Event { + id: sub_id, + msg: EventMsg::Error(ErrorEvent { + message: format!("failed to download remote skill {hazelnut_id}: {err}"), + codex_error_info: Some(CodexErrorInfo::Other), + }), + }; + sess.send_event_raw(event).await; + } + } + } + pub async fn undo(sess: &Arc, sub_id: String) { let turn_context = sess.new_default_turn_with_sub_id(sub_id).await; sess.spawn_task(turn_context, Vec::new(), UndoTask::new()) diff --git a/codex-rs/core/src/rollout/policy.rs b/codex-rs/core/src/rollout/policy.rs index 5796bd1961..587b68a913 100644 --- a/codex-rs/core/src/rollout/policy.rs +++ b/codex-rs/core/src/rollout/policy.rs @@ -90,6 +90,8 @@ pub(crate) fn should_persist_event_msg(ev: &EventMsg) -> bool { | EventMsg::McpStartupComplete(_) | EventMsg::ListCustomPromptsResponse(_) | EventMsg::ListSkillsResponse(_) + | EventMsg::ListRemoteSkillsResponse(_) + | EventMsg::RemoteSkillDownloaded(_) | EventMsg::PlanUpdate(_) | EventMsg::ShutdownComplete | EventMsg::ViewImageToolCall(_) diff --git a/codex-rs/core/src/skills/mod.rs b/codex-rs/core/src/skills/mod.rs index 4d79bca258..6148092a12 100644 --- a/codex-rs/core/src/skills/mod.rs +++ b/codex-rs/core/src/skills/mod.rs @@ -3,6 +3,7 @@ pub mod injection; pub mod loader; pub mod manager; pub mod model; +pub mod remote; pub mod render; pub mod system; diff --git a/codex-rs/core/src/skills/remote.rs b/codex-rs/core/src/skills/remote.rs new file mode 100644 index 0000000000..af6dd5593a --- /dev/null +++ b/codex-rs/core/src/skills/remote.rs @@ -0,0 +1,314 @@ +use anyhow::Context; +use anyhow::Result; +use serde::Deserialize; +use std::collections::HashMap; +use std::collections::HashSet; +use std::path::Component; +use std::path::Path; +use std::path::PathBuf; +use std::time::Duration; + +use crate::config::Config; +use crate::default_client::build_reqwest_client; + +const REMOTE_SKILLS_API_TIMEOUT: Duration = Duration::from_secs(30); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemoteSkillSummary { + pub id: String, + pub name: String, + pub description: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemoteSkillDownload { + pub id: String, + pub name: String, + pub base_sediment_id: String, + pub files: HashMap, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemoteSkillDownloadResult { + pub id: String, + pub name: String, + pub path: PathBuf, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RemoteSkillFileRange { + pub start: u64, + pub length: u64, +} + +#[derive(Debug, Deserialize)] +struct RemoteSkillsResponse { + hazelnuts: Vec, +} + +#[derive(Debug, Deserialize)] +struct RemoteSkill { + id: String, + name: String, + description: String, +} + +#[derive(Debug, Deserialize)] +struct RemoteSkillsDownloadResponse { + hazelnuts: Vec, +} + +#[derive(Debug, Deserialize)] +struct RemoteSkillDownloadPayload { + id: String, + name: String, + #[serde(rename = "base_sediment_id")] + base_sediment_id: String, + files: HashMap, +} + +#[derive(Debug, Deserialize)] +struct RemoteSkillFileRangePayload { + start: u64, + length: u64, +} + +pub async fn list_remote_skills(config: &Config) -> Result> { + let base_url = config.chatgpt_base_url.trim_end_matches('/'); + let base_url = base_url.strip_suffix("/backend-api").unwrap_or(base_url); + let url = format!("{base_url}/public-api/hazelnuts/"); + + let client = build_reqwest_client(); + let response = client + .get(&url) + .timeout(REMOTE_SKILLS_API_TIMEOUT) + .query(&[("product_surface", "codex")]) + .send() + .await + .with_context(|| format!("Failed to send request to {url}"))?; + + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + if !status.is_success() { + anyhow::bail!("Request failed with status {status} from {url}: {body}"); + } + + let parsed: RemoteSkillsResponse = + serde_json::from_str(&body).context("Failed to parse skills response")?; + + Ok(parsed + .hazelnuts + .into_iter() + .map(|skill| RemoteSkillSummary { + id: skill.id, + name: skill.name, + description: skill.description, + }) + .collect()) +} + +pub async fn download_remote_skill( + config: &Config, + hazelnut_id: &str, + is_preload: bool, +) -> Result { + let hazelnut = fetch_remote_skill(config, hazelnut_id).await?; + + let client = build_reqwest_client(); + let base_url = config.chatgpt_base_url.trim_end_matches('/'); + let base_url = base_url.strip_suffix("/backend-api").unwrap_or(base_url); + let url = format!("{base_url}/public-api/hazelnuts/{hazelnut_id}/export"); + let response = client + .get(&url) + .timeout(REMOTE_SKILLS_API_TIMEOUT) + .send() + .await + .with_context(|| format!("Failed to send download request to {url}"))?; + + let status = response.status(); + let body = response.bytes().await.context("Failed to read download")?; + if !status.is_success() { + let body_text = String::from_utf8_lossy(&body); + anyhow::bail!("Download failed with status {status} from {url}: {body_text}"); + } + + if !is_zip_payload(&body) { + anyhow::bail!("Downloaded remote skill payload is not a zip archive"); + } + + let preferred_dir_name = if hazelnut.name.trim().is_empty() { + None + } else { + Some(hazelnut.name.as_str()) + }; + let dir_name = preferred_dir_name + .and_then(validate_dir_name_format) + .or_else(|| validate_dir_name_format(&hazelnut.id)) + .ok_or_else(|| anyhow::anyhow!("Remote skill has no valid directory name"))?; + let output_root = if is_preload { + config + .codex_home + .join("vendor_imports") + .join("skills") + .join("skills") + .join(".curated") + } else { + config.codex_home.join("skills").join("downloaded") + }; + let output_dir = output_root.join(dir_name); + tokio::fs::create_dir_all(&output_dir) + .await + .context("Failed to create downloaded skills directory")?; + + let allowed_files = hazelnut.files.keys().cloned().collect::>(); + let zip_bytes = body.to_vec(); + let output_dir_clone = output_dir.clone(); + let prefix_candidates = vec![hazelnut.name.clone(), hazelnut.id.clone()]; + tokio::task::spawn_blocking(move || { + extract_zip_to_dir( + zip_bytes, + &output_dir_clone, + &allowed_files, + &prefix_candidates, + ) + }) + .await + .context("Zip extraction task failed")??; + + Ok(RemoteSkillDownloadResult { + id: hazelnut.id, + name: hazelnut.name, + path: output_dir, + }) +} + +fn safe_join(base: &Path, name: &str) -> Result { + let path = Path::new(name); + for component in path.components() { + match component { + Component::Normal(_) => {} + _ => { + anyhow::bail!("Invalid file path in remote skill payload: {name}"); + } + } + } + Ok(base.join(path)) +} + +fn validate_dir_name_format(name: &str) -> Option { + let mut components = Path::new(name).components(); + match (components.next(), components.next()) { + (Some(Component::Normal(component)), None) => { + let value = component.to_string_lossy().to_string(); + if value.is_empty() { None } else { Some(value) } + } + _ => None, + } +} + +fn is_zip_payload(bytes: &[u8]) -> bool { + bytes.starts_with(b"PK\x03\x04") + || bytes.starts_with(b"PK\x05\x06") + || bytes.starts_with(b"PK\x07\x08") +} + +fn extract_zip_to_dir( + bytes: Vec, + output_dir: &Path, + allowed_files: &HashSet, + prefix_candidates: &[String], +) -> Result<()> { + let cursor = std::io::Cursor::new(bytes); + let mut archive = zip::ZipArchive::new(cursor).context("Failed to open zip archive")?; + for i in 0..archive.len() { + let mut file = archive.by_index(i).context("Failed to read zip entry")?; + if file.is_dir() { + continue; + } + let raw_name = file.name().to_string(); + let normalized = normalize_zip_name(&raw_name, prefix_candidates); + let Some(normalized) = normalized else { + continue; + }; + if !allowed_files.contains(&normalized) { + continue; + } + let file_path = safe_join(output_dir, &normalized)?; + if let Some(parent) = file_path.parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("Failed to create parent dir for {normalized}"))?; + } + let mut out = std::fs::File::create(&file_path) + .with_context(|| format!("Failed to create file {normalized}"))?; + std::io::copy(&mut file, &mut out) + .with_context(|| format!("Failed to write skill file {normalized}"))?; + } + Ok(()) +} + +fn normalize_zip_name(name: &str, prefix_candidates: &[String]) -> Option { + let mut trimmed = name.trim_start_matches("./"); + for prefix in prefix_candidates { + if prefix.is_empty() { + continue; + } + let prefix = format!("{prefix}/"); + if let Some(rest) = trimmed.strip_prefix(&prefix) { + trimmed = rest; + break; + } + } + if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + } +} + +async fn fetch_remote_skill(config: &Config, hazelnut_id: &str) -> Result { + let base_url = config.chatgpt_base_url.trim_end_matches('/'); + let base_url = base_url.strip_suffix("/backend-api").unwrap_or(base_url); + let url = format!("{base_url}/public-api/hazelnuts/"); + + let client = build_reqwest_client(); + let response = client + .get(&url) + .timeout(REMOTE_SKILLS_API_TIMEOUT) + .query(&[("product_surface", "codex")]) + .send() + .await + .with_context(|| format!("Failed to send request to {url}"))?; + + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + if !status.is_success() { + anyhow::bail!("Request failed with status {status} from {url}: {body}"); + } + + let parsed: RemoteSkillsDownloadResponse = + serde_json::from_str(&body).context("Failed to parse skills response")?; + let hazelnut = parsed + .hazelnuts + .into_iter() + .find(|hazelnut| hazelnut.id == hazelnut_id) + .ok_or_else(|| anyhow::anyhow!("Remote skill {hazelnut_id} not found"))?; + + Ok(RemoteSkillDownload { + id: hazelnut.id, + name: hazelnut.name, + base_sediment_id: hazelnut.base_sediment_id, + files: hazelnut + .files + .into_iter() + .map(|(name, range)| { + ( + name, + RemoteSkillFileRange { + start: range.start, + length: range.length, + }, + ) + }) + .collect(), + }) +} diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 3b4a3bf86b..9175b100a5 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -773,6 +773,8 @@ impl EventProcessor for EventProcessorWithHumanOutput { | EventMsg::McpListToolsResponse(_) | EventMsg::ListCustomPromptsResponse(_) | EventMsg::ListSkillsResponse(_) + | EventMsg::ListRemoteSkillsResponse(_) + | EventMsg::RemoteSkillDownloaded(_) | EventMsg::RawResponseItem(_) | EventMsg::UserMessage(_) | EventMsg::EnteredReviewMode(_) diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 6273607a22..6513c9e6f7 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -322,6 +322,8 @@ async fn run_codex_tool_session_inner( | EventMsg::McpListToolsResponse(_) | EventMsg::ListCustomPromptsResponse(_) | EventMsg::ListSkillsResponse(_) + | EventMsg::ListRemoteSkillsResponse(_) + | EventMsg::RemoteSkillDownloaded(_) | EventMsg::ExecCommandBegin(_) | EventMsg::TerminalInteraction(_) | EventMsg::ExecCommandOutputDelta(_) diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 140d3931e0..078ea31d18 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -269,6 +269,15 @@ pub enum Op { force_reload: bool, }, + /// Request the list of remote skills available via ChatGPT sharing. + ListRemoteSkills, + + /// Download a remote skill by id into the local skills cache. + DownloadRemoteSkill { + hazelnut_id: String, + is_preload: bool, + }, + /// Request the agent to summarize the current conversation context. /// The agent will use its existing context (either conversation history or previous response id) /// to generate a summary which will be returned as an AgentMessage event. @@ -822,6 +831,12 @@ pub enum EventMsg { /// List of skills available to the agent. ListSkillsResponse(ListSkillsResponseEvent), + /// List of remote skills available to the agent. + ListRemoteSkillsResponse(ListRemoteSkillsResponseEvent), + + /// Remote skill downloaded to local cache. + RemoteSkillDownloaded(RemoteSkillDownloadedEvent), + /// Notification that skill data may have been updated and clients may want to reload. SkillsUpdateAvailable, @@ -2127,6 +2142,27 @@ pub struct ListSkillsResponseEvent { pub skills: Vec, } +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] +pub struct RemoteSkillSummary { + pub id: String, + pub name: String, + pub description: String, +} + +/// Response payload for `Op::ListRemoteSkills`. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] +pub struct ListRemoteSkillsResponseEvent { + pub skills: Vec, +} + +/// Response payload for `Op::DownloadRemoteSkill`. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] +pub struct RemoteSkillDownloadedEvent { + pub id: String, + pub name: String, + pub path: PathBuf, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(rename_all = "snake_case")] diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index a952b3354c..40247c35a4 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -3526,6 +3526,7 @@ impl ChatWidget { EventMsg::McpListToolsResponse(ev) => self.on_list_mcp_tools(ev), EventMsg::ListCustomPromptsResponse(ev) => self.on_list_custom_prompts(ev), EventMsg::ListSkillsResponse(ev) => self.on_list_skills(ev), + EventMsg::ListRemoteSkillsResponse(_) | EventMsg::RemoteSkillDownloaded(_) => {} EventMsg::SkillsUpdateAvailable => { self.submit_op(Op::ListSkills { cwds: Vec::new(), From e3d39013d328ce76fb15b0ce30fc6b4d9c9ed727 Mon Sep 17 00:00:00 2001 From: Rasmus Rygaard Date: Tue, 3 Feb 2026 14:38:21 -0800 Subject: [PATCH 124/139] Handle exec shutdown on Interrupt (fixes immortal `codex exec` with websockets) (#10519) ### Motivation - Ensure `codex exec` exits when a running turn is interrupted (e.g., Ctrl-C) so the CLI is not "immortal" when websockets/streaming are used. ### Description - Return `CodexStatus::InitiateShutdown` when handling `EventMsg::TurnAborted` in `exec/src/event_processor_with_human_output.rs` so human-output exec mode shuts down after an interrupt. - Treat `protocol::EventMsg::TurnAborted` as `CodexStatus::InitiateShutdown` in `exec/src/event_processor_with_jsonl_output.rs` so JSONL output mode behaves the same. - Applied formatting with `just fmt`. ### Testing - Ran `just fmt` successfully. - Ran `cargo test -p codex-exec`; many unit tests ran and the test command completed, but the full test run in this environment produced `35 passed, 11 failed` where the failures are due to Landlock sandbox panics and 403 responses in the test harness (environmental/integration issues) and are not caused by the interrupt/shutdown changes. ------ [Codex Task](https://chatgpt.com/codex/tasks/task_i_698165cec4e083258d17702bd29014c1) --- .../src/event_processor_with_human_output.rs | 23 +++++++++++-------- .../src/event_processor_with_jsonl_output.rs | 1 + 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 9175b100a5..cbe45b92f1 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -593,17 +593,20 @@ impl EventProcessor for EventProcessorWithHumanOutput { view.path.display() ); } - EventMsg::TurnAborted(abort_reason) => match abort_reason.reason { - TurnAbortReason::Interrupted => { - ts_msg!(self, "task interrupted"); + EventMsg::TurnAborted(abort_reason) => { + match abort_reason.reason { + TurnAbortReason::Interrupted => { + ts_msg!(self, "task interrupted"); + } + TurnAbortReason::Replaced => { + ts_msg!(self, "task aborted: replaced by a new task"); + } + TurnAbortReason::ReviewEnded => { + ts_msg!(self, "task aborted: review ended"); + } } - TurnAbortReason::Replaced => { - ts_msg!(self, "task aborted: replaced by a new task"); - } - TurnAbortReason::ReviewEnded => { - ts_msg!(self, "task aborted: review ended"); - } - }, + return CodexStatus::InitiateShutdown; + } EventMsg::ContextCompacted(_) => { ts_msg!(self, "context compacted"); } diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output.rs b/codex-rs/exec/src/event_processor_with_jsonl_output.rs index 823fa7b810..9675651ef7 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output.rs @@ -871,6 +871,7 @@ impl EventProcessor for EventProcessorWithJsonOutput { } CodexStatus::InitiateShutdown } + protocol::EventMsg::TurnAborted(_) => CodexStatus::InitiateShutdown, protocol::EventMsg::ShutdownComplete => CodexStatus::Shutdown, _ => CodexStatus::Running, } From 750ebe154d7c497dbac28977b21a28285db6201a Mon Sep 17 00:00:00 2001 From: Shijie Rao Date: Tue, 3 Feb 2026 14:53:36 -0800 Subject: [PATCH 125/139] Feat: add upgrade to app server modelList (#10556) ### Summary * Add model upgrade to listModel app server endpoint to support dynamically show model upgrade banner. --- .../schema/json/codex_app_server_protocol.schemas.json | 6 ++++++ .../schema/json/v2/ModelListResponse.json | 6 ++++++ codex-rs/app-server-protocol/schema/typescript/v2/Model.ts | 2 +- codex-rs/app-server-protocol/src/protocol/v2.rs | 1 + codex-rs/app-server/README.md | 2 +- codex-rs/app-server/src/models.rs | 1 + codex-rs/app-server/tests/suite/v2/model_list.rs | 4 ++++ codex-rs/docs/codex_mcp_interface.md | 1 + 8 files changed, 21 insertions(+), 2 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 83a7c781b0..b813b5946c 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -11938,6 +11938,12 @@ "supportsPersonality": { "default": false, "type": "boolean" + }, + "upgrade": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json index 8c8126bb6b..a1b3f7114c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json @@ -59,6 +59,12 @@ "supportsPersonality": { "default": false, "type": "boolean" + }, + "upgrade": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts index 72daa93edd..7528a8fad3 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts @@ -5,4 +5,4 @@ import type { InputModality } from "../InputModality"; import type { ReasoningEffort } from "../ReasoningEffort"; import type { ReasoningEffortOption } from "./ReasoningEffortOption"; -export type Model = { id: string, model: string, displayName: string, description: string, supportedReasoningEfforts: Array, defaultReasoningEffort: ReasoningEffort, inputModalities: Array, supportsPersonality: boolean, isDefault: boolean, }; +export type Model = { id: string, model: string, upgrade: string | null, displayName: string, description: string, supportedReasoningEfforts: Array, defaultReasoningEffort: ReasoningEffort, inputModalities: Array, supportsPersonality: boolean, isDefault: boolean, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 67e7013a31..dbc51fa9f9 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -998,6 +998,7 @@ pub struct ModelListParams { pub struct Model { pub id: String, pub model: String, + pub upgrade: Option, pub display_name: String, pub description: String, pub supported_reasoning_efforts: Vec, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index d6fae48466..78a4f3cf94 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -90,7 +90,7 @@ Example (from OpenAI's official VSCode extension): - `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`. - `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). -- `model/list` — list available models (with reasoning effort options). +- `model/list` — list available models (with reasoning effort options and optional `upgrade` model ids). - `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). - `skills/list` — list skills for one or more `cwd` values (optional `forceReload`). - `skills/remote/read` — list public remote skills (**under development; do not call from production clients yet**). diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index 133dc73cc0..350b86f92a 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -22,6 +22,7 @@ fn model_from_preset(preset: ModelPreset) -> Model { Model { id: preset.id.to_string(), model: preset.model.to_string(), + upgrade: preset.upgrade.map(|upgrade| upgrade.id), display_name: preset.display_name.to_string(), description: preset.description.to_string(), supported_reasoning_efforts: reasoning_efforts_from_preset( diff --git a/codex-rs/app-server/tests/suite/v2/model_list.rs b/codex-rs/app-server/tests/suite/v2/model_list.rs index c3b4ec7088..c2431d24d0 100644 --- a/codex-rs/app-server/tests/suite/v2/model_list.rs +++ b/codex-rs/app-server/tests/suite/v2/model_list.rs @@ -51,6 +51,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.2-codex".to_string(), model: "gpt-5.2-codex".to_string(), + upgrade: None, display_name: "gpt-5.2-codex".to_string(), description: "Latest frontier agentic coding model.".to_string(), supported_reasoning_efforts: vec![ @@ -80,6 +81,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.1-codex-max".to_string(), model: "gpt-5.1-codex-max".to_string(), + upgrade: Some("gpt-5.2-codex".to_string()), display_name: "gpt-5.1-codex-max".to_string(), description: "Codex-optimized flagship for deep and fast reasoning.".to_string(), supported_reasoning_efforts: vec![ @@ -109,6 +111,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.1-codex-mini".to_string(), model: "gpt-5.1-codex-mini".to_string(), + upgrade: Some("gpt-5.2-codex".to_string()), display_name: "gpt-5.1-codex-mini".to_string(), description: "Optimized for codex. Cheaper, faster, but less capable.".to_string(), supported_reasoning_efforts: vec![ @@ -130,6 +133,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.2".to_string(), model: "gpt-5.2".to_string(), + upgrade: Some("gpt-5.2-codex".to_string()), display_name: "gpt-5.2".to_string(), description: "Latest frontier model with improvements across knowledge, reasoning and coding" diff --git a/codex-rs/docs/codex_mcp_interface.md b/codex-rs/docs/codex_mcp_interface.md index edd693d5ed..8847b6b4ee 100644 --- a/codex-rs/docs/codex_mcp_interface.md +++ b/codex-rs/docs/codex_mcp_interface.md @@ -100,6 +100,7 @@ Each response yields: - `defaultReasoningEffort` – suggested effort for the UI - `supportsPersonality` – whether the model supports personality-specific instructions - `isDefault` – whether the model is recommended for most users + - `upgrade` – optional recommended upgrade model id - `nextCursor` – pass into the next request to continue paging (optional) ## Collaboration modes (experimental) From aec58ac29b8c02d32e0e034ae2703f7a0fd48c21 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 3 Feb 2026 15:01:51 -0800 Subject: [PATCH 126/139] feat(tui): pace catch-up stream chunking with hysteresis (#10461) ## Summary - preserve baseline streaming behavior (smooth mode still commits one line per 50ms tick) - extract adaptive chunking policy and commit-tick orchestration from ChatWidget into `streaming/chunking.rs` and `streaming/commit_tick.rs` - add hysteresis-based catch-up behavior with bounded batch draining to reduce queue lag without bursty single-frame jumps - document policy behavior, tuning guidance, and debug flow in rustdoc + docs ## Testing - just fmt - cargo test -p codex-tui --- codex-rs/tui/src/app.rs | 7 +- codex-rs/tui/src/chatwidget.rs | 68 ++-- codex-rs/tui/src/chatwidget/tests.rs | 1 + codex-rs/tui/src/pager_overlay.rs | 3 +- codex-rs/tui/src/streaming/chunking.rs | 439 +++++++++++++++++++++ codex-rs/tui/src/streaming/commit_tick.rs | 224 +++++++++++ codex-rs/tui/src/streaming/controller.rs | 46 +++ codex-rs/tui/src/streaming/mod.rs | 84 +++- codex-rs/tui/src/tui.rs | 4 + codex-rs/tui/src/tui/frame_rate_limiter.rs | 6 +- codex-rs/tui/src/tui/frame_requester.rs | 10 +- docs/tui-stream-chunking-review.md | 124 ++++++ docs/tui-stream-chunking-tuning.md | 98 +++++ docs/tui-stream-chunking-validation.md | 105 +++++ 14 files changed, 1182 insertions(+), 37 deletions(-) create mode 100644 codex-rs/tui/src/streaming/chunking.rs create mode 100644 codex-rs/tui/src/streaming/commit_tick.rs create mode 100644 docs/tui-stream-chunking-review.md create mode 100644 docs/tui-stream-chunking-tuning.md create mode 100644 docs/tui-stream-chunking-validation.md diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 0160e8f23d..878d4d5da2 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -102,6 +102,11 @@ use toml::Value as TomlValue; const EXTERNAL_EDITOR_HINT: &str = "Save and close external editor to continue."; const THREAD_EVENT_CHANNEL_CAPACITY: usize = 32768; +/// Baseline cadence for periodic stream commit animation ticks. +/// +/// Smooth-mode streaming drains one line per tick, so this interval controls +/// perceived typing speed for non-backlogged output. +const COMMIT_ANIMATION_TICK: Duration = tui::TARGET_FRAME_INTERVAL; #[derive(Debug, Clone)] pub struct AppExitInfo { @@ -1485,7 +1490,7 @@ impl App { let running = self.commit_anim_running.clone(); thread::spawn(move || { while running.load(Ordering::Relaxed) { - thread::sleep(Duration::from_millis(50)); + thread::sleep(COMMIT_ANIMATION_TICK); tx.send(AppEvent::CommitTick); } }); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 40247c35a4..52a1b54404 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -196,6 +196,9 @@ mod skills; use self::skills::collect_tool_mentions; use self::skills::find_app_mentions; use self::skills::find_skill_mentions_with_tool_mentions; +use crate::streaming::chunking::AdaptiveChunkingPolicy; +use crate::streaming::commit_tick::CommitTickScope; +use crate::streaming::commit_tick::run_commit_tick; use crate::streaming::controller::PlanStreamController; use crate::streaming::controller::StreamController; @@ -485,6 +488,7 @@ pub(crate) struct ChatWidget { rate_limit_warnings: RateLimitWarningState, rate_limit_switch_prompt: RateLimitSwitchPromptState, rate_limit_poller: Option>, + adaptive_chunking: AdaptiveChunkingPolicy, // Stream lifecycle controller stream_controller: Option, // Stream lifecycle controller for proposed plan output. @@ -769,6 +773,7 @@ impl ChatWidget { { self.add_boxed_history(cell); } + self.adaptive_chunking.reset(); } /// Update the status indicator header and details. @@ -943,6 +948,7 @@ impl ChatWidget { && controller.push(&delta) { self.app_event_tx.send(AppEvent::StartCommitAnimation); + self.run_catch_up_commit_tick(); } self.request_redraw(); } @@ -1020,6 +1026,7 @@ impl ChatWidget { self.saw_plan_item_this_turn = false; self.plan_delta_buffer.clear(); self.plan_item_active = false; + self.adaptive_chunking.reset(); self.plan_stream_controller = None; self.otel_manager.reset_runtime_metrics(); self.bottom_pane.clear_quit_shortcut_hint(); @@ -1286,7 +1293,9 @@ impl ChatWidget { self.last_unified_wait = None; self.unified_exec_wait_streak = None; self.clear_unified_exec_processes(); + self.adaptive_chunking.reset(); self.stream_controller = None; + self.plan_stream_controller = None; self.maybe_show_pending_rate_limit_prompt(); } @@ -1838,30 +1847,41 @@ impl ChatWidget { self.set_status(message, additional_details); } - /// Periodic tick to commit at most one queued line to history with a small delay, - /// animating the output. + /// Periodic tick for stream commits. In smooth mode this preserves one-line pacing, while + /// catch-up mode drains larger batches to reduce queue lag. pub(crate) fn on_commit_tick(&mut self) { - let mut has_controller = false; - let mut all_idle = true; - if let Some(controller) = self.stream_controller.as_mut() { - has_controller = true; - let (cell, is_idle) = controller.on_commit_tick(); - if let Some(cell) = cell { - self.bottom_pane.hide_status_indicator(); - self.add_boxed_history(cell); - } - all_idle &= is_idle; + self.run_commit_tick(); + } + + /// Runs a regular periodic commit tick. + fn run_commit_tick(&mut self) { + self.run_commit_tick_with_scope(CommitTickScope::AnyMode); + } + + /// Runs an opportunistic commit tick only if catch-up mode is active. + fn run_catch_up_commit_tick(&mut self) { + self.run_commit_tick_with_scope(CommitTickScope::CatchUpOnly); + } + + /// Runs a commit tick for the current stream queue snapshot. + /// + /// `scope` controls whether this call may commit in smooth mode or only when catch-up + /// is currently active. + fn run_commit_tick_with_scope(&mut self, scope: CommitTickScope) { + let now = Instant::now(); + let outcome = run_commit_tick( + &mut self.adaptive_chunking, + self.stream_controller.as_mut(), + self.plan_stream_controller.as_mut(), + scope, + now, + ); + for cell in outcome.cells { + self.bottom_pane.hide_status_indicator(); + self.add_boxed_history(cell); } - if let Some(controller) = self.plan_stream_controller.as_mut() { - has_controller = true; - let (cell, is_idle) = controller.on_commit_tick(); - if let Some(cell) = cell { - self.bottom_pane.hide_status_indicator(); - self.add_boxed_history(cell); - } - all_idle &= is_idle; - } - if has_controller && all_idle { + + if outcome.has_controller && outcome.all_idle { self.app_event_tx.send(AppEvent::StopCommitAnimation); } } @@ -1930,6 +1950,7 @@ impl ChatWidget { && controller.push(&delta) { self.app_event_tx.send(AppEvent::StartCommitAnimation); + self.run_catch_up_commit_tick(); } self.request_redraw(); } @@ -2268,6 +2289,7 @@ impl ChatWidget { rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), @@ -2413,6 +2435,7 @@ impl ChatWidget { rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), @@ -2547,6 +2570,7 @@ impl ChatWidget { rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index d080140a32..dba8bf82e1 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -852,6 +852,7 @@ async fn make_chatwidget_manual( rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: crate::streaming::chunking::AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), diff --git a/codex-rs/tui/src/pager_overlay.rs b/codex-rs/tui/src/pager_overlay.rs index 5f3f79e320..58f15fa7f3 100644 --- a/codex-rs/tui/src/pager_overlay.rs +++ b/codex-rs/tui/src/pager_overlay.rs @@ -17,7 +17,6 @@ use std::io::Result; use std::sync::Arc; -use std::time::Duration; use crate::chatwidget::ActiveCellTranscriptKey; use crate::history_cell::HistoryCell; @@ -299,7 +298,7 @@ impl PagerView { } } tui.frame_requester() - .schedule_frame_in(Duration::from_millis(16)); + .schedule_frame_in(crate::tui::TARGET_FRAME_INTERVAL); Ok(()) } diff --git a/codex-rs/tui/src/streaming/chunking.rs b/codex-rs/tui/src/streaming/chunking.rs new file mode 100644 index 0000000000..b653f51ecb --- /dev/null +++ b/codex-rs/tui/src/streaming/chunking.rs @@ -0,0 +1,439 @@ +//! Adaptive stream chunking policy for commit animation ticks. +//! +//! This policy preserves the baseline user experience while adapting to bursty +//! stream input. In [`ChunkingMode::Smooth`], one queued line is drained per +//! baseline commit tick. When queue pressure rises, it switches to +//! [`ChunkingMode::CatchUp`] and drains queued backlog immediately so display +//! lag converges as quickly as possible. +//! +//! The policy is source-agnostic: it depends only on queue depth and queue +//! age from [`QueueSnapshot`]. It does not branch on source identity or explicit +//! throughput targets. +//! +//! # Mental model +//! +//! Think of this as a two-gear system: +//! +//! - [`ChunkingMode::Smooth`]: steady baseline display pacing. +//! - [`ChunkingMode::CatchUp`]: full queue draining while backlog exists. +//! +//! The transition logic intentionally uses hysteresis: +//! +//! - enter catch-up on higher-pressure thresholds +//! - exit catch-up on lower-pressure thresholds, held for [`EXIT_HOLD`] +//! - after exit, suppress immediate re-entry for [`REENTER_CATCH_UP_HOLD`] +//! unless backlog is severe +//! +//! This avoids rapid gear-flapping near threshold boundaries. +//! +//! # Policy flow +//! +//! On each decision tick, [`AdaptiveChunkingPolicy::decide`] does: +//! +//! 1. If queue is empty, reset to [`ChunkingMode::Smooth`]. +//! 2. If currently smooth, call [`AdaptiveChunkingPolicy::maybe_enter_catch_up`]. +//! 3. If currently catch-up, call [`AdaptiveChunkingPolicy::maybe_exit_catch_up`]. +//! 4. Build [`DrainPlan`] (`Single` for smooth, `Batch(queued_lines)` for catch-up). +//! +//! # Concrete examples +//! +//! With current defaults: +//! +//! - `Smooth` drains one line per commit tick. +//! - `CatchUp` drains all currently queued lines in a tick. +//! +//! # Tuning guide (in code terms) +//! +//! Prefer tuning in this order so causes remain clear: +//! +//! 1. enter/exit thresholds: [`ENTER_QUEUE_DEPTH_LINES`], [`ENTER_OLDEST_AGE`], +//! [`EXIT_QUEUE_DEPTH_LINES`], [`EXIT_OLDEST_AGE`] +//! 2. hysteresis windows: [`EXIT_HOLD`], [`REENTER_CATCH_UP_HOLD`] +//! 3. severe gates: [`SEVERE_QUEUE_DEPTH_LINES`], [`SEVERE_OLDEST_AGE`] +//! +//! Symptom-oriented adjustments: +//! +//! - lag starts too late: lower enter thresholds +//! - frequent smooth/catch-up chatter: increase hold windows, or tighten exit +//! thresholds +//! - catch-up re-enters too eagerly after exit: increase re-entry hold +//! +//! # Responsibilities +//! +//! - track mode and hysteresis state +//! - produce deterministic [`ChunkingDecision`] values from queue snapshots +//! - preserve queue order by draining from queue head only +//! +//! # Non-responsibilities +//! +//! - scheduling commit ticks +//! - reordering stream lines +//! - transport/source-specific semantics +//! +//! Markdown docs remain supplemental: +//! +//! - `docs/tui-stream-chunking-review.md` +//! - `docs/tui-stream-chunking-tuning.md` +//! - `docs/tui-stream-chunking-validation.md` + +use std::time::Duration; +use std::time::Instant; + +/// Queue-depth threshold that allows entering catch-up mode. +/// +/// Crossing this threshold alone is sufficient to leave smooth mode. +const ENTER_QUEUE_DEPTH_LINES: usize = 8; + +/// Oldest-line age threshold that allows entering catch-up mode. +/// +/// Crossing this threshold alone is sufficient to leave smooth mode. +const ENTER_OLDEST_AGE: Duration = Duration::from_millis(120); + +/// Queue-depth threshold used when evaluating catch-up exit hysteresis. +/// +/// Depth must be at or below this value before exit hold timing can begin. +const EXIT_QUEUE_DEPTH_LINES: usize = 2; + +/// Oldest-line age threshold used when evaluating catch-up exit hysteresis. +/// +/// Age must be at or below this value before exit hold timing can begin. +const EXIT_OLDEST_AGE: Duration = Duration::from_millis(40); + +/// Minimum duration queue pressure must stay below exit thresholds to leave catch-up mode. +const EXIT_HOLD: Duration = Duration::from_millis(250); + +/// Cooldown window after a catch-up exit that suppresses immediate re-entry. +/// +/// Severe backlog still bypasses this hold to avoid unbounded queue-age growth. +const REENTER_CATCH_UP_HOLD: Duration = Duration::from_millis(250); + +/// Queue-depth cutoff that marks backlog as severe for faster convergence. +/// +/// This threshold is used to bypass re-entry hold after a recent catch-up exit. +const SEVERE_QUEUE_DEPTH_LINES: usize = 64; + +/// Oldest-line age cutoff that marks backlog as severe for faster convergence. +const SEVERE_OLDEST_AGE: Duration = Duration::from_millis(300); + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(crate) enum ChunkingMode { + /// Drain one line per baseline commit tick. + #[default] + Smooth, + /// Drain multiple lines per tick according to queue pressure. + CatchUp, +} + +/// Captures queue pressure inputs used by adaptive chunking decisions. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(crate) struct QueueSnapshot { + /// Number of queued stream lines waiting to be displayed. + pub(crate) queued_lines: usize, + /// Age of the oldest queued line at decision time. + pub(crate) oldest_age: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum DrainPlan { + /// Emit exactly one queued line. + Single, + /// Emit up to `usize` queued lines. + Batch(usize), +} + +/// Represents one policy decision for a specific queue snapshot. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct ChunkingDecision { + /// Mode after applying hysteresis transitions for this decision. + pub(crate) mode: ChunkingMode, + /// Whether this decision transitioned from `Smooth` into `CatchUp`. + pub(crate) entered_catch_up: bool, + /// Drain plan to execute for the current commit tick. + pub(crate) drain_plan: DrainPlan, +} + +/// Maintains adaptive chunking mode and hysteresis state across ticks. +#[derive(Debug, Default)] +pub(crate) struct AdaptiveChunkingPolicy { + mode: ChunkingMode, + below_exit_threshold_since: Option, + last_catch_up_exit_at: Option, +} + +impl AdaptiveChunkingPolicy { + /// Returns the policy mode used by the most recent decision. + pub(crate) fn mode(&self) -> ChunkingMode { + self.mode + } + + /// Resets state to baseline smooth mode. + pub(crate) fn reset(&mut self) { + self.mode = ChunkingMode::Smooth; + self.below_exit_threshold_since = None; + self.last_catch_up_exit_at = None; + } + + /// Computes a drain decision from the current queue snapshot. + /// + /// The decision is deterministic for a given `(mode, snapshot, now)` triple. Callers should + /// avoid inventing synthetic snapshots; stale queue age data can cause premature catch-up exits. + pub(crate) fn decide(&mut self, snapshot: QueueSnapshot, now: Instant) -> ChunkingDecision { + if snapshot.queued_lines == 0 { + self.note_catch_up_exit(now); + self.mode = ChunkingMode::Smooth; + self.below_exit_threshold_since = None; + return ChunkingDecision { + mode: self.mode, + entered_catch_up: false, + drain_plan: DrainPlan::Single, + }; + } + + let entered_catch_up = match self.mode { + ChunkingMode::Smooth => self.maybe_enter_catch_up(snapshot, now), + ChunkingMode::CatchUp => { + self.maybe_exit_catch_up(snapshot, now); + false + } + }; + + let drain_plan = match self.mode { + ChunkingMode::Smooth => DrainPlan::Single, + ChunkingMode::CatchUp => DrainPlan::Batch(snapshot.queued_lines.max(1)), + }; + + ChunkingDecision { + mode: self.mode, + entered_catch_up, + drain_plan, + } + } + + /// Switches from `Smooth` to `CatchUp` when enter thresholds are crossed. + /// + /// Returns `true` only on the transition tick so callers can emit one-shot + /// transition observability. + fn maybe_enter_catch_up(&mut self, snapshot: QueueSnapshot, now: Instant) -> bool { + if !should_enter_catch_up(snapshot) { + return false; + } + if self.reentry_hold_active(now) && !is_severe_backlog(snapshot) { + return false; + } + self.mode = ChunkingMode::CatchUp; + self.below_exit_threshold_since = None; + self.last_catch_up_exit_at = None; + true + } + + /// Applies exit hysteresis while in `CatchUp` mode. + /// + /// The policy requires queue pressure to stay below exit thresholds for the + /// full `EXIT_HOLD` window before returning to `Smooth`. + fn maybe_exit_catch_up(&mut self, snapshot: QueueSnapshot, now: Instant) { + if !should_exit_catch_up(snapshot) { + self.below_exit_threshold_since = None; + return; + } + + match self.below_exit_threshold_since { + Some(since) if now.saturating_duration_since(since) >= EXIT_HOLD => { + self.mode = ChunkingMode::Smooth; + self.below_exit_threshold_since = None; + self.last_catch_up_exit_at = Some(now); + } + Some(_) => {} + None => { + self.below_exit_threshold_since = Some(now); + } + } + } + + fn note_catch_up_exit(&mut self, now: Instant) { + if self.mode == ChunkingMode::CatchUp { + self.last_catch_up_exit_at = Some(now); + } + } + + fn reentry_hold_active(&self, now: Instant) -> bool { + self.last_catch_up_exit_at + .is_some_and(|exit| now.saturating_duration_since(exit) < REENTER_CATCH_UP_HOLD) + } +} + +/// Returns whether current queue pressure warrants entering catch-up mode. +/// +/// Either depth or age pressure is sufficient to trigger catch-up. +fn should_enter_catch_up(snapshot: QueueSnapshot) -> bool { + snapshot.queued_lines >= ENTER_QUEUE_DEPTH_LINES + || snapshot + .oldest_age + .is_some_and(|oldest| oldest >= ENTER_OLDEST_AGE) +} + +/// Returns whether queue pressure is low enough to begin exit hysteresis. +/// +/// Both depth and age must be below thresholds; this prevents oscillation when +/// one signal is still under load. +fn should_exit_catch_up(snapshot: QueueSnapshot) -> bool { + snapshot.queued_lines <= EXIT_QUEUE_DEPTH_LINES + && snapshot + .oldest_age + .is_some_and(|oldest| oldest <= EXIT_OLDEST_AGE) +} + +/// Returns whether backlog is severe enough to use a faster catch-up target. +/// +/// Severe pressure bypasses re-entry hold to avoid queue-age growth after a +/// recent catch-up exit. +fn is_severe_backlog(snapshot: QueueSnapshot) -> bool { + snapshot.queued_lines >= SEVERE_QUEUE_DEPTH_LINES + || snapshot + .oldest_age + .is_some_and(|oldest| oldest >= SEVERE_OLDEST_AGE) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + fn snapshot(queued_lines: usize, oldest_age_ms: u64) -> QueueSnapshot { + QueueSnapshot { + queued_lines, + oldest_age: Some(Duration::from_millis(oldest_age_ms)), + } + } + + #[test] + fn smooth_mode_is_default() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + + let decision = policy.decide(snapshot(1, 10), now); + assert_eq!(decision.mode, ChunkingMode::Smooth); + assert_eq!(decision.entered_catch_up, false); + assert_eq!(decision.drain_plan, DrainPlan::Single); + } + + #[test] + fn enters_catch_up_on_depth_threshold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + + let decision = policy.decide(snapshot(8, 10), now); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.entered_catch_up, true); + assert_eq!(decision.drain_plan, DrainPlan::Batch(8)); + } + + #[test] + fn enters_catch_up_on_age_threshold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + + let decision = policy.decide(snapshot(2, 120), now); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.entered_catch_up, true); + assert_eq!(decision.drain_plan, DrainPlan::Batch(2)); + } + + #[test] + fn severe_backlog_uses_faster_paced_batches() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + let _ = policy.decide(snapshot(9, 10), now); + + let decision = policy.decide(snapshot(64, 10), now + Duration::from_millis(5)); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.drain_plan, DrainPlan::Batch(64)); + } + + #[test] + fn catch_up_batch_drains_current_backlog() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + let decision = policy.decide(snapshot(512, 400), now); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.drain_plan, DrainPlan::Batch(512)); + } + + #[test] + fn exits_catch_up_after_hysteresis_hold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let t0 = Instant::now(); + + let _ = policy.decide(snapshot(9, 10), t0); + assert_eq!(policy.mode(), ChunkingMode::CatchUp); + + let pre_hold = policy.decide(snapshot(2, 40), t0 + Duration::from_millis(200)); + assert_eq!(pre_hold.mode, ChunkingMode::CatchUp); + + let post_hold = policy.decide(snapshot(2, 40), t0 + Duration::from_millis(460)); + assert_eq!(post_hold.mode, ChunkingMode::Smooth); + assert_eq!(post_hold.drain_plan, DrainPlan::Single); + } + + #[test] + fn drops_back_to_smooth_when_idle() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + let _ = policy.decide(snapshot(9, 10), now); + assert_eq!(policy.mode(), ChunkingMode::CatchUp); + + let decision = policy.decide( + QueueSnapshot { + queued_lines: 0, + oldest_age: None, + }, + now + Duration::from_millis(20), + ); + assert_eq!(decision.mode, ChunkingMode::Smooth); + assert_eq!(decision.drain_plan, DrainPlan::Single); + } + + #[test] + fn holds_reentry_after_catch_up_exit() { + let mut policy = AdaptiveChunkingPolicy::default(); + let t0 = Instant::now(); + + let entered = policy.decide(snapshot(8, 20), t0); + assert_eq!(entered.mode, ChunkingMode::CatchUp); + + let drained = policy.decide( + QueueSnapshot { + queued_lines: 0, + oldest_age: None, + }, + t0 + Duration::from_millis(20), + ); + assert_eq!(drained.mode, ChunkingMode::Smooth); + + let held = policy.decide(snapshot(8, 20), t0 + Duration::from_millis(120)); + assert_eq!(held.mode, ChunkingMode::Smooth); + assert_eq!(held.drain_plan, DrainPlan::Single); + + let reentered = policy.decide(snapshot(8, 20), t0 + Duration::from_millis(320)); + assert_eq!(reentered.mode, ChunkingMode::CatchUp); + assert_eq!(reentered.drain_plan, DrainPlan::Batch(8)); + } + + #[test] + fn severe_backlog_can_reenter_during_hold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let t0 = Instant::now(); + + let _ = policy.decide(snapshot(8, 20), t0); + let _ = policy.decide( + QueueSnapshot { + queued_lines: 0, + oldest_age: None, + }, + t0 + Duration::from_millis(20), + ); + + let severe = policy.decide(snapshot(64, 20), t0 + Duration::from_millis(120)); + assert_eq!(severe.mode, ChunkingMode::CatchUp); + assert_eq!(severe.drain_plan, DrainPlan::Batch(64)); + } +} diff --git a/codex-rs/tui/src/streaming/commit_tick.rs b/codex-rs/tui/src/streaming/commit_tick.rs new file mode 100644 index 0000000000..bda63bccf6 --- /dev/null +++ b/codex-rs/tui/src/streaming/commit_tick.rs @@ -0,0 +1,224 @@ +//! Orchestrates commit-tick drains across streaming controllers. +//! +//! This module bridges queue-based chunking policy (`chunking`) with the concrete stream +//! controllers (`controller`). Callers provide the current controllers and tick scope; the module +//! computes queue pressure, selects a drain plan, applies it, and returns emitted history cells. +//! +//! The module preserves ordering by draining only from controller queue heads. It does not schedule +//! ticks and it does not mutate UI state directly; callers remain responsible for animation events +//! and history insertion side effects. +//! +//! The main flow is: +//! [`run_commit_tick`] -> [`stream_queue_snapshot`] -> [`QueueSnapshot`] -> +//! [`resolve_chunking_plan`] -> [`ChunkingDecision`]/[`DrainPlan`] -> +//! [`apply_commit_tick_plan`] -> [`CommitTickOutput`]. + +use std::time::Duration; +use std::time::Instant; + +use crate::history_cell::HistoryCell; + +use super::chunking::AdaptiveChunkingPolicy; +use super::chunking::ChunkingDecision; +use super::chunking::ChunkingMode; +use super::chunking::DrainPlan; +use super::chunking::QueueSnapshot; +use super::controller::PlanStreamController; +use super::controller::StreamController; + +/// Describes whether a commit tick may run in all modes or only in catch-up mode. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum CommitTickScope { + /// Always run the tick, regardless of current chunking mode. + AnyMode, + /// Run model transitions and policy updates, but commit lines only in `CatchUp`. + CatchUpOnly, +} + +/// Describes what a single commit tick produced. +pub(crate) struct CommitTickOutput { + /// Cells produced by drained stream lines during this tick. + pub(crate) cells: Vec>, + /// Whether at least one stream controller was present for this tick. + pub(crate) has_controller: bool, + /// Whether all present controllers were idle after this tick. + pub(crate) all_idle: bool, +} + +impl Default for CommitTickOutput { + /// Creates an output that represents "no commit performed". + /// + /// This is used when a tick is intentionally suppressed, for example when + /// the scope is [`CommitTickScope::CatchUpOnly`] and policy is not in catch-up mode. + fn default() -> Self { + Self { + cells: Vec::new(), + has_controller: false, + all_idle: true, + } + } +} + +/// Runs one commit tick against the provided stream controllers. +/// +/// This function collects a [`QueueSnapshot`], asks [`AdaptiveChunkingPolicy`] for a +/// [`ChunkingDecision`], and then applies the resulting [`DrainPlan`] to both controllers. +/// If callers pass stale controller references (for example, references not tied to the +/// current turn), queue age can be misread and the policy may stay in catch-up longer +/// than expected. +pub(crate) fn run_commit_tick( + policy: &mut AdaptiveChunkingPolicy, + stream_controller: Option<&mut StreamController>, + plan_stream_controller: Option<&mut PlanStreamController>, + scope: CommitTickScope, + now: Instant, +) -> CommitTickOutput { + let snapshot = stream_queue_snapshot( + stream_controller.as_deref(), + plan_stream_controller.as_deref(), + now, + ); + let decision = resolve_chunking_plan(policy, snapshot, now); + if scope == CommitTickScope::CatchUpOnly && decision.mode != ChunkingMode::CatchUp { + return CommitTickOutput::default(); + } + + let output = apply_commit_tick_plan( + decision.drain_plan, + stream_controller, + plan_stream_controller, + ); + tracing::trace!( + mode = ?decision.mode, + queued_lines = snapshot.queued_lines, + oldest_queued_age_ms = snapshot.oldest_age.map(|age| age.as_millis() as u64), + drain_plan = ?decision.drain_plan, + has_controller = output.has_controller, + all_idle = output.all_idle, + "stream chunking commit tick" + ); + output +} + +/// Builds the combined queue-pressure snapshot consumed by chunking policy. +/// +/// The snapshot sums queue depth across controllers and keeps the maximum oldest age +/// so policy decisions reflect the most delayed queued line currently visible. +fn stream_queue_snapshot( + stream_controller: Option<&StreamController>, + plan_stream_controller: Option<&PlanStreamController>, + now: Instant, +) -> QueueSnapshot { + let mut queued_lines = 0usize; + let mut oldest_age: Option = None; + + if let Some(controller) = stream_controller { + queued_lines += controller.queued_lines(); + oldest_age = max_duration(oldest_age, controller.oldest_queued_age(now)); + } + if let Some(controller) = plan_stream_controller { + queued_lines += controller.queued_lines(); + oldest_age = max_duration(oldest_age, controller.oldest_queued_age(now)); + } + + QueueSnapshot { + queued_lines, + oldest_age, + } +} + +/// Computes one policy decision and emits a trace log on mode transitions. +/// +/// This keeps policy transition logging in one place so callers can rely on +/// [`run_commit_tick`] to provide consistent observability. +fn resolve_chunking_plan( + policy: &mut AdaptiveChunkingPolicy, + snapshot: QueueSnapshot, + now: Instant, +) -> ChunkingDecision { + let prior_mode = policy.mode(); + let decision = policy.decide(snapshot, now); + if decision.mode != prior_mode { + tracing::trace!( + prior_mode = ?prior_mode, + new_mode = ?decision.mode, + queued_lines = snapshot.queued_lines, + oldest_queued_age_ms = snapshot.oldest_age.map(|age| age.as_millis() as u64), + entered_catch_up = decision.entered_catch_up, + "stream chunking mode transition" + ); + } + decision +} + +/// Applies a [`DrainPlan`] to all available stream controllers. +/// +/// The returned [`CommitTickOutput`] reports emitted cells and whether all +/// present controllers are idle after draining. +fn apply_commit_tick_plan( + drain_plan: DrainPlan, + stream_controller: Option<&mut StreamController>, + plan_stream_controller: Option<&mut PlanStreamController>, +) -> CommitTickOutput { + let mut output = CommitTickOutput::default(); + + if let Some(controller) = stream_controller { + output.has_controller = true; + let (cell, is_idle) = drain_stream_controller(controller, drain_plan); + if let Some(cell) = cell { + output.cells.push(cell); + } + output.all_idle &= is_idle; + } + if let Some(controller) = plan_stream_controller { + output.has_controller = true; + let (cell, is_idle) = drain_plan_stream_controller(controller, drain_plan); + if let Some(cell) = cell { + output.cells.push(cell); + } + output.all_idle &= is_idle; + } + + output +} + +/// Applies one drain step to the main stream controller. +/// +/// [`DrainPlan::Single`] maps to one-line drain; [`DrainPlan::Batch`] maps to +/// multi-line drain (including instant catch-up when policy requests the full +/// queued backlog). +fn drain_stream_controller( + controller: &mut StreamController, + drain_plan: DrainPlan, +) -> (Option>, bool) { + match drain_plan { + DrainPlan::Single => controller.on_commit_tick(), + DrainPlan::Batch(max_lines) => controller.on_commit_tick_batch(max_lines), + } +} + +/// Applies one drain step to the plan stream controller. +/// +/// This mirrors [`drain_stream_controller`] so both controller types follow the +/// same chunking policy decisions. +fn drain_plan_stream_controller( + controller: &mut PlanStreamController, + drain_plan: DrainPlan, +) -> (Option>, bool) { + match drain_plan { + DrainPlan::Single => controller.on_commit_tick(), + DrainPlan::Batch(max_lines) => controller.on_commit_tick_batch(max_lines), + } +} + +/// Returns the greater of two optional durations. +/// +/// This helper preserves whichever side is present when only one duration exists. +fn max_duration(lhs: Option, rhs: Option) -> Option { + match (lhs, rhs) { + (Some(left), Some(right)) => Some(left.max(right)), + (Some(left), None) => Some(left), + (None, Some(right)) => Some(right), + (None, None) => None, + } +} diff --git a/codex-rs/tui/src/streaming/controller.rs b/codex-rs/tui/src/streaming/controller.rs index 462962e980..6117485adf 100644 --- a/codex-rs/tui/src/streaming/controller.rs +++ b/codex-rs/tui/src/streaming/controller.rs @@ -4,6 +4,8 @@ use crate::render::line_utils::prefix_lines; use crate::style::proposed_plan_style; use ratatui::prelude::Stylize; use ratatui::text::Line; +use std::time::Duration; +use std::time::Instant; use super::StreamState; @@ -71,6 +73,28 @@ impl StreamController { (self.emit(step), self.state.is_idle()) } + /// Step animation: commit at most `max_lines` queued lines. + /// + /// This is intended for adaptive catch-up drains. Callers should keep `max_lines` bounded; a + /// very large value can collapse perceived animation into a single jump. + pub(crate) fn on_commit_tick_batch( + &mut self, + max_lines: usize, + ) -> (Option>, bool) { + let step = self.state.drain_n(max_lines.max(1)); + (self.emit(step), self.state.is_idle()) + } + + /// Returns the current number of queued lines waiting to be displayed. + pub(crate) fn queued_lines(&self) -> usize { + self.state.queued_len() + } + + /// Returns the age of the oldest queued line. + pub(crate) fn oldest_queued_age(&self, now: Instant) -> Option { + self.state.oldest_queued_age(now) + } + fn emit(&mut self, lines: Vec>) -> Option> { if lines.is_empty() { return None; @@ -142,6 +166,28 @@ impl PlanStreamController { (self.emit(step, false), self.state.is_idle()) } + /// Step animation: commit at most `max_lines` queued lines. + /// + /// This is intended for adaptive catch-up drains. Callers should keep `max_lines` bounded; a + /// very large value can collapse perceived animation into a single jump. + pub(crate) fn on_commit_tick_batch( + &mut self, + max_lines: usize, + ) -> (Option>, bool) { + let step = self.state.drain_n(max_lines.max(1)); + (self.emit(step, false), self.state.is_idle()) + } + + /// Returns the current number of queued plan lines waiting to be displayed. + pub(crate) fn queued_lines(&self) -> usize { + self.state.queued_len() + } + + /// Returns the age of the oldest queued plan line. + pub(crate) fn oldest_queued_age(&self, now: Instant) -> Option { + self.state.oldest_queued_age(now) + } + fn emit( &mut self, lines: Vec>, diff --git a/codex-rs/tui/src/streaming/mod.rs b/codex-rs/tui/src/streaming/mod.rs index fa00702834..c783f27ae9 100644 --- a/codex-rs/tui/src/streaming/mod.rs +++ b/codex-rs/tui/src/streaming/mod.rs @@ -1,17 +1,39 @@ +//! Streaming primitives used by the TUI transcript pipeline. +//! +//! `StreamState` owns newline-gated markdown collection and a FIFO queue of committed render lines. +//! Higher-level modules build on top of this state: +//! - `controller` adapts queued lines into `HistoryCell` emission rules for message and plan streams. +//! - `chunking` computes adaptive drain plans from queue pressure. +//! - `commit_tick` binds policy decisions to concrete controller drains. +//! +//! The key invariant is queue ordering. All drains pop from the front, and enqueue records an +//! arrival timestamp so policy code can reason about oldest queued age without peeking into text. + use std::collections::VecDeque; +use std::time::Duration; +use std::time::Instant; use ratatui::text::Line; use crate::markdown_stream::MarkdownStreamCollector; +pub(crate) mod chunking; +pub(crate) mod commit_tick; pub(crate) mod controller; +struct QueuedLine { + line: Line<'static>, + enqueued_at: Instant, +} + +/// Holds in-flight markdown stream state and queued committed lines. pub(crate) struct StreamState { pub(crate) collector: MarkdownStreamCollector, - queued_lines: VecDeque>, + queued_lines: VecDeque, pub(crate) has_seen_delta: bool, } impl StreamState { + /// Creates an empty stream state with an optional target wrap width. pub(crate) fn new(width: Option) -> Self { Self { collector: MarkdownStreamCollector::new(width), @@ -19,21 +41,75 @@ impl StreamState { has_seen_delta: false, } } + /// Resets collector and queue state for the next stream lifecycle. pub(crate) fn clear(&mut self) { self.collector.clear(); self.queued_lines.clear(); self.has_seen_delta = false; } + /// Drains one queued line from the front of the queue. pub(crate) fn step(&mut self) -> Vec> { - self.queued_lines.pop_front().into_iter().collect() + self.queued_lines + .pop_front() + .map(|queued| queued.line) + .into_iter() + .collect() } + /// Drains up to `max_lines` queued lines from the front of the queue. + /// + /// Callers that pass very large values still get bounded behavior because this method clamps to + /// the currently available queue length. + pub(crate) fn drain_n(&mut self, max_lines: usize) -> Vec> { + let end = max_lines.min(self.queued_lines.len()); + self.queued_lines + .drain(..end) + .map(|queued| queued.line) + .collect() + } + /// Drains all queued lines from the front of the queue. pub(crate) fn drain_all(&mut self) -> Vec> { - self.queued_lines.drain(..).collect() + self.queued_lines + .drain(..) + .map(|queued| queued.line) + .collect() } + /// Returns whether no lines are queued for commit. pub(crate) fn is_idle(&self) -> bool { self.queued_lines.is_empty() } + /// Returns the current queue depth. + pub(crate) fn queued_len(&self) -> usize { + self.queued_lines.len() + } + /// Returns the age of the oldest queued line. + pub(crate) fn oldest_queued_age(&self, now: Instant) -> Option { + self.queued_lines + .front() + .map(|queued| now.saturating_duration_since(queued.enqueued_at)) + } + /// Appends committed lines to the queue with a shared enqueue timestamp. pub(crate) fn enqueue(&mut self, lines: Vec>) { - self.queued_lines.extend(lines); + let now = Instant::now(); + self.queued_lines + .extend(lines.into_iter().map(|line| QueuedLine { + line, + enqueued_at: now, + })); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn drain_n_clamps_to_available_lines() { + let mut state = StreamState::new(None); + state.enqueue(vec![Line::from("one")]); + + let drained = state.drain_n(8); + assert_eq!(drained, vec![Line::from("one")]); + assert!(state.is_idle()); } } diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index 761fa83627..a77785c1b8 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -10,6 +10,7 @@ use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; +use std::time::Duration; use crossterm::Command; use crossterm::SynchronizedUpdate; @@ -52,6 +53,9 @@ mod frame_requester; #[cfg(unix)] mod job_control; +/// Target frame interval for UI redraw scheduling. +pub(crate) const TARGET_FRAME_INTERVAL: Duration = frame_rate_limiter::MIN_FRAME_INTERVAL; + /// A type alias for the terminal type used in this application pub type Terminal = CustomTerminal>; diff --git a/codex-rs/tui/src/tui/frame_rate_limiter.rs b/codex-rs/tui/src/tui/frame_rate_limiter.rs index 56dd752e64..6f4ad8e747 100644 --- a/codex-rs/tui/src/tui/frame_rate_limiter.rs +++ b/codex-rs/tui/src/tui/frame_rate_limiter.rs @@ -1,7 +1,7 @@ //! Limits how frequently frame draw notifications may be emitted. //! //! Widgets sometimes call `FrameRequester::schedule_frame()` more frequently than a user can -//! perceive. This limiter clamps draw notifications to a maximum of 60 FPS to avoid wasted work. +//! perceive. This limiter clamps draw notifications to a maximum of 120 FPS to avoid wasted work. //! //! This is intentionally a small, pure helper so it can be unit-tested in isolation and used by //! the async frame scheduler without adding complexity to the app/event loop. @@ -9,8 +9,8 @@ use std::time::Duration; use std::time::Instant; -/// A 60 FPS minimum frame interval (≈16.67ms). -pub(super) const MIN_FRAME_INTERVAL: Duration = Duration::from_nanos(16_666_667); +/// A 120 FPS minimum frame interval (≈8.33ms). +pub(super) const MIN_FRAME_INTERVAL: Duration = Duration::from_nanos(8_333_334); /// Remembers the most recent emitted draw, allowing deadlines to be clamped forward. #[derive(Debug, Default)] diff --git a/codex-rs/tui/src/tui/frame_requester.rs b/codex-rs/tui/src/tui/frame_requester.rs index 88db9d71f3..d7e54d82cc 100644 --- a/codex-rs/tui/src/tui/frame_requester.rs +++ b/codex-rs/tui/src/tui/frame_requester.rs @@ -71,7 +71,7 @@ impl FrameRequester { /// /// This type is internal to `FrameRequester` and is spawned as a task to handle scheduling logic. /// -/// To avoid wasted redraw work, draw notifications are clamped to a maximum of 60 FPS (see +/// To avoid wasted redraw work, draw notifications are clamped to a maximum of 120 FPS (see /// [`FrameRateLimiter`]). struct FrameScheduler { receiver: mpsc::UnboundedReceiver, @@ -232,7 +232,7 @@ mod tests { } #[tokio::test(flavor = "current_thread", start_paused = true)] - async fn test_limits_draw_notifications_to_60fps() { + async fn test_limits_draw_notifications_to_120fps() { let (draw_tx, mut draw_rx) = broadcast::channel(16); let requester = FrameRequester::new(draw_tx); @@ -250,7 +250,7 @@ mod tests { let early = draw_rx.recv().timeout(Duration::from_millis(1)).await; assert!( early.is_err(), - "draw fired too early; expected max 60fps (min interval {MIN_FRAME_INTERVAL:?})" + "draw fired too early; expected max 120fps (min interval {MIN_FRAME_INTERVAL:?})" ); time::advance(MIN_FRAME_INTERVAL).await; @@ -278,11 +278,11 @@ mod tests { requester.schedule_frame_in(Duration::from_millis(1)); - time::advance(Duration::from_millis(10)).await; + time::advance(MIN_FRAME_INTERVAL / 2).await; let too_early = draw_rx.recv().timeout(Duration::from_millis(1)).await; assert!( too_early.is_err(), - "draw fired too early; expected max 60fps (min interval {MIN_FRAME_INTERVAL:?})" + "draw fired too early; expected max 120fps (min interval {MIN_FRAME_INTERVAL:?})" ); time::advance(MIN_FRAME_INTERVAL).await; diff --git a/docs/tui-stream-chunking-review.md b/docs/tui-stream-chunking-review.md new file mode 100644 index 0000000000..3722492ddf --- /dev/null +++ b/docs/tui-stream-chunking-review.md @@ -0,0 +1,124 @@ +# TUI Stream Chunking + +This document explains how stream chunking in the TUI works and why it is +implemented this way. + +## Problem + +Streaming output can arrive faster than a one-line-per-tick animation can show +it. If commit speed stays fixed while arrival speed spikes, queued lines grow +and visible output lags behind received output. + +## Design goals + +- Preserve existing baseline behavior under normal load. +- Reduce display lag when backlog builds. +- Keep output order stable. +- Avoid abrupt single-frame flushes that look jumpy. +- Keep policy transport-agnostic and based only on queue state. + +## Non-goals + +- The policy does not schedule animation ticks. +- The policy does not depend on upstream source identity. +- The policy does not reorder queued output. + +## Where the logic lives + +- `codex-rs/tui/src/streaming/chunking.rs` + - Adaptive policy, mode transitions, and drain-plan selection. +- `codex-rs/tui/src/streaming/commit_tick.rs` + - Orchestration for each commit tick: snapshot, decide, drain, trace. +- `codex-rs/tui/src/streaming/controller.rs` + - Queue/drain primitives used by commit-tick orchestration. +- `codex-rs/tui/src/chatwidget.rs` + - Integration point that invokes commit-tick orchestration and handles UI + lifecycle events. + +## Runtime flow + +On each commit tick: + +1. Build a queue snapshot across active controllers. + - `queued_lines`: total queued lines. + - `oldest_age`: max age of the oldest queued line across controllers. +2. Ask adaptive policy for a decision. + - Output: current mode and a drain plan. +3. Apply drain plan to each controller. +4. Emit drained `HistoryCell`s for insertion by the caller. +5. Emit trace logs for observability. + +In `CatchUpOnly` scope, policy state still advances, but draining is skipped +unless mode is currently `CatchUp`. + +## Modes and transitions + +Two modes are used: + +- `Smooth` + - Baseline behavior: one line drained per baseline commit tick. + - Baseline tick interval currently comes from + `tui/src/app.rs:COMMIT_ANIMATION_TICK` (~8.3ms, ~120fps). +- `CatchUp` + - Drain current queued backlog per tick via `Batch(queued_lines)`. + +Entry and exit use hysteresis: + +- Enter `CatchUp` when queue depth or queue age exceeds enter thresholds. +- Exit requires both depth and age to be below exit thresholds for a hold + window (`EXIT_HOLD`). + +This prevents oscillation when load hovers near thresholds. + +## Current experimental tuning values + +These are the current values in `streaming/chunking.rs` plus the baseline +commit tick in `tui/src/app.rs`. They are +experimental and may change as we gather more trace data. + +- Baseline commit tick: `~8.3ms` (`COMMIT_ANIMATION_TICK` in `app.rs`) +- Enter catch-up: + - `queued_lines >= 8` OR `oldest_age >= 120ms` +- Exit catch-up eligibility: + - `queued_lines <= 2` AND `oldest_age <= 40ms` +- Exit hold (`CatchUp -> Smooth`): `250ms` +- Re-entry hold after catch-up exit: `250ms` +- Severe backlog thresholds: + - `queued_lines >= 64` OR `oldest_age >= 300ms` + +## Drain planning + +In `Smooth`, plan is always `Single`. + +In `CatchUp`, plan is `Batch(queued_lines)`, which drains the currently queued +backlog for immediate convergence. + +## Why this design + +This keeps normal animation semantics intact, while making backlog behavior +adaptive: + +- Under normal load, behavior stays familiar and stable. +- Under pressure, queue age is reduced quickly without sacrificing ordering. +- Hysteresis avoids rapid mode flapping. + +## Invariants + +- Queue order is preserved. +- Empty queue resets policy back to `Smooth`. +- `CatchUp` exits only after sustained low pressure. +- Catch-up drains are immediate while in `CatchUp`. + +## Observability + +Trace events are emitted from commit-tick orchestration: + +- `stream chunking commit tick` + - `mode`, `queued_lines`, `oldest_queued_age_ms`, `drain_plan`, + `has_controller`, `all_idle` +- `stream chunking mode transition` + - `prior_mode`, `new_mode`, `queued_lines`, `oldest_queued_age_ms`, + `entered_catch_up` + +These events are intended to explain display lag by showing queue pressure, +selected drain behavior, and mode transitions over time. diff --git a/docs/tui-stream-chunking-tuning.md b/docs/tui-stream-chunking-tuning.md new file mode 100644 index 0000000000..d9a2ea5e21 --- /dev/null +++ b/docs/tui-stream-chunking-tuning.md @@ -0,0 +1,98 @@ +# TUI Stream Chunking Tuning Guide + +This document explains how to tune adaptive stream chunking constants without +changing the underlying policy shape. + +## Scope + +Use this guide when adjusting queue-pressure thresholds and hysteresis windows in +`codex-rs/tui/src/streaming/chunking.rs`, and baseline commit cadence in +`codex-rs/tui/src/app.rs`. + +This guide is about tuning behavior, not redesigning the policy. + +## Before tuning + +- Keep the baseline behavior intact: + - `Smooth` mode drains one line per baseline tick. + - `CatchUp` mode drains queued backlog immediately. +- Capture trace logs with: + - `codex_tui::streaming::commit_tick` +- Evaluate on sustained, bursty, and mixed-output prompts. + +See `docs/tui-stream-chunking-validation.md` for the measurement process. + +## Tuning goals + +Tune for all three goals together: + +- low visible lag under bursty output +- low mode flapping (`Smooth <-> CatchUp` chatter) +- stable catch-up entry/exit behavior under mixed workloads + +## Constants and what they control + +### Baseline commit cadence + +- `COMMIT_ANIMATION_TICK` (`tui/src/app.rs`) + - Lower values increase smooth-mode update cadence and reduce steady-state lag. + - Higher values increase smoothing and can increase perceived lag. + - This should usually move after chunking thresholds/holds are in a good range. + +### Enter/exit thresholds + +- `ENTER_QUEUE_DEPTH_LINES`, `ENTER_OLDEST_AGE` + - Lower values enter catch-up earlier (less lag, more mode switching risk). + - Higher values enter later (more lag tolerance, fewer mode switches). +- `EXIT_QUEUE_DEPTH_LINES`, `EXIT_OLDEST_AGE` + - Lower values keep catch-up active longer. + - Higher values allow earlier exit and may increase re-entry churn. + +### Hysteresis holds + +- `EXIT_HOLD` + - Longer hold reduces flip-flop exits when pressure is noisy. + - Too long can keep catch-up active after pressure has cleared. +- `REENTER_CATCH_UP_HOLD` + - Longer hold suppresses rapid re-entry after exit. + - Too long can delay needed catch-up for near-term bursts. + - Severe backlog bypasses this hold by design. + +### Severe-backlog gates + +- `SEVERE_QUEUE_DEPTH_LINES`, `SEVERE_OLDEST_AGE` + - Lower values bypass re-entry hold earlier. + - Higher values reserve hold bypass for only extreme pressure. + +## Recommended tuning order + +Tune in this order to keep cause/effect clear: + +1. Entry/exit thresholds (`ENTER_*`, `EXIT_*`) +2. Hold windows (`EXIT_HOLD`, `REENTER_CATCH_UP_HOLD`) +3. Severe gates (`SEVERE_*`) +4. Baseline cadence (`COMMIT_ANIMATION_TICK`) + +Change one logical group at a time and re-measure before the next group. + +## Symptom-driven adjustments + +- Too much lag before catch-up starts: + - lower `ENTER_QUEUE_DEPTH_LINES` and/or `ENTER_OLDEST_AGE` +- Frequent `Smooth -> CatchUp -> Smooth` chatter: + - increase `EXIT_HOLD` + - increase `REENTER_CATCH_UP_HOLD` + - tighten exit thresholds (lower `EXIT_*`) +- Catch-up engages too often for short bursts: + - increase `ENTER_QUEUE_DEPTH_LINES` and/or `ENTER_OLDEST_AGE` + - increase `REENTER_CATCH_UP_HOLD` +- Catch-up engages too late: + - lower `ENTER_QUEUE_DEPTH_LINES` and/or `ENTER_OLDEST_AGE` + - lower severe gates (`SEVERE_*`) to bypass re-entry hold sooner + +## Validation checklist after each tuning pass + +- `cargo test -p codex-tui` passes. +- Trace window shows bounded queue-age behavior. +- Mode transitions are not concentrated in repeated short-interval cycles. +- Catch-up clears backlog quickly once mode enters `CatchUp`. diff --git a/docs/tui-stream-chunking-validation.md b/docs/tui-stream-chunking-validation.md new file mode 100644 index 0000000000..26bd949da6 --- /dev/null +++ b/docs/tui-stream-chunking-validation.md @@ -0,0 +1,105 @@ +# TUI Stream Chunking Validation Process + +This document records the process used to validate adaptive stream chunking +and anti-flap behavior. + +## Scope + +The goal is to verify two properties from runtime traces: + +- display lag is reduced when queue pressure rises +- mode transitions remain stable instead of rapidly flapping + +## Trace targets + +Chunking observability is emitted by: + +- `codex_tui::streaming::commit_tick` + +Two trace messages are used: + +- `stream chunking commit tick` +- `stream chunking mode transition` + +## Runtime command + +Run Codex with chunking traces enabled: + +```bash +RUST_LOG='codex_tui::streaming::commit_tick=trace,codex_tui=info,codex_core=info,codex_rmcp_client=info' \ + just codex --enable=responses_websockets +``` + +## Log capture process + +1. Record the current size of `~/.codex/log/codex-tui.log` as a start offset. +2. Run an interactive prompt that produces sustained streamed output. +3. Stop the run. +4. Parse only log bytes written after the recorded offset. + +This avoids mixing earlier sessions with the current measurement window. + +## Metrics reviewed + +For each measured window: + +- `commit_ticks` +- `mode_transitions` +- `smooth_ticks` +- `catchup_ticks` +- drain-plan distribution (`Single`, `Batch(n)`) +- queue depth (`max`, `p95`, `p99`) +- oldest queued age (`max`, `p95`, `p99`) +- rapid re-entry count: + - number of `Smooth -> CatchUp` transitions within 1 second of a + `CatchUp -> Smooth` transition + +## Interpretation + +- Healthy behavior: + - queue age remains bounded while backlog is drained + - transition count is low relative to total ticks + - rapid re-entry events are infrequent and localized to burst boundaries +- Regressed behavior: + - repeated short-interval mode toggles across an extended window + - persistent queue-age growth while in smooth mode + - long catch-up runs without backlog reduction + +## Experiment history + +This section captures the major tuning passes so future work can build on +what has already been tried. + +- Baseline + - One-line smooth draining with a 50ms commit tick. + - This preserved familiar pacing but could feel laggy under sustained + backlog. +- Pass 1: instant catch-up, baseline tick unchanged + - Kept smooth-mode semantics but made catch-up drain the full queued + backlog each catch-up tick. + - Result: queue lag dropped faster, but perceived motion could still feel + stepped because smooth-mode cadence remained coarse. +- Pass 2: faster baseline tick (25ms) + - Improved smooth-mode cadence and reduced visible stepping. + - Result: better, but still not aligned with draw cadence. +- Pass 3: frame-aligned baseline tick (~16.7ms) + - Set baseline commit cadence to approximately 60fps. + - Result: smoother perceived progression while retaining hysteresis and + fast backlog convergence. +- Pass 4: higher frame-aligned baseline tick (~8.3ms) + - Set baseline commit cadence to approximately 120fps. + - Result: further reduced smooth-mode stepping while preserving the same + adaptive catch-up policy shape. + +Current state combines: + +- instant catch-up draining in `CatchUp` +- hysteresis for mode-entry/exit stability +- frame-aligned smooth-mode commit cadence (~8.3ms) + +## Notes + +- Validation is source-agnostic and does not rely on naming any specific + upstream provider. +- This process intentionally preserves existing baseline smooth behavior and + focuses on burst/backlog handling behavior. From 16647b188b3a8988b1d27d1bd517854b4762bf19 Mon Sep 17 00:00:00 2001 From: Celia Chen Date: Tue, 3 Feb 2026 15:17:34 -0800 Subject: [PATCH 127/139] chore: add `codex debug app-server` tooling (#10367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit codex debug app-server forwards the message through codex-app-server-test-client’s send_message_v2 library entry point, using std::env::current_exe() to resolve the codex binary. for how it looks like, see: ``` celia@com-92114 codex-rs % cargo build -p codex-cli && target/debug/codex debug app-server --help Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.34s Tooling: helps debug the app server Usage: codex debug app-server [OPTIONS] Commands: send-message-v2 help Print this message or the help of the given subcommand(s) ```` and ``` celia@com-92114 codex-rs % cargo build -p codex-cli && target/debug/codex debug app-server send-message-v2 "hello world" Compiling codex-cli v0.0.0 (/Users/celia/code/codex/codex-rs/cli) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.38s > { > "method": "initialize", > "id": "f8ba9f60-3a49-4ea9-81d6-4ab6853e3954", > "params": { > "clientInfo": { > "name": "codex-toy-app-server", > "title": "Codex Toy App Server", > "version": "0.0.0" > }, > "capabilities": { > "experimentalApi": true > } > } > } < { < "id": "f8ba9f60-3a49-4ea9-81d6-4ab6853e3954", < "result": { < "userAgent": "codex-toy-app-server/0.0.0 (Mac OS 26.2.0; arm64) vscode/2.4.27 (codex-toy-app-server; 0.0.0)" < } < } < initialize response: InitializeResponse { user_agent: "codex-toy-app-server/0.0.0 (Mac OS 26.2.0; arm64) vscode/2.4.27 (codex-toy-app-server; 0.0.0)" } > { > "method": "thread/start", > "id": "203f1630-beee-4e60-b17b-9eff16b1638b", > "params": { > "model": null, > "modelProvider": null, > "cwd": null, > "approvalPolicy": null, > "sandbox": null, > "config": null, > "baseInstructions": null, > "developerInstructions": null, > "personality": null, > "ephemeral": null, > "dynamicTools": null, > "mockExperimentalField": null, > "experimentalRawEvents": false > } > } ... ``` --- codex-rs/Cargo.lock | 1 + codex-rs/Cargo.toml | 1 + codex-rs/app-server-test-client/BUILD.bazel | 2 +- codex-rs/app-server-test-client/src/lib.rs | 1057 ++++++++++++++++++ codex-rs/app-server-test-client/src/main.rs | 1058 +------------------ codex-rs/cli/Cargo.toml | 1 + codex-rs/cli/src/main.rs | 48 +- 7 files changed, 1109 insertions(+), 1059 deletions(-) create mode 100644 codex-rs/app-server-test-client/src/lib.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 3d9daeb7ed..43b4b295a8 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1379,6 +1379,7 @@ dependencies = [ "clap_complete", "codex-app-server", "codex-app-server-protocol", + "codex-app-server-test-client", "codex-arg0", "codex-chatgpt", "codex-cloud-tasks", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 2c92f0b463..39ebe5967d 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -70,6 +70,7 @@ codex-ansi-escape = { path = "ansi-escape" } codex-api = { path = "codex-api" } codex-app-server = { path = "app-server" } codex-app-server-protocol = { path = "app-server-protocol" } +codex-app-server-test-client = { path = "app-server-test-client" } codex-apply-patch = { path = "apply-patch" } codex-arg0 = { path = "arg0" } codex-async-utils = { path = "async-utils" } diff --git a/codex-rs/app-server-test-client/BUILD.bazel b/codex-rs/app-server-test-client/BUILD.bazel index e3610747cd..3a1686a04e 100644 --- a/codex-rs/app-server-test-client/BUILD.bazel +++ b/codex-rs/app-server-test-client/BUILD.bazel @@ -1,6 +1,6 @@ load("//:defs.bzl", "codex_rust_crate") codex_rust_crate( - name = "codex-app-server-test-client", + name = "app-server-test-client", crate_name = "codex_app_server_test_client", ) diff --git a/codex-rs/app-server-test-client/src/lib.rs b/codex-rs/app-server-test-client/src/lib.rs new file mode 100644 index 0000000000..90f6adf572 --- /dev/null +++ b/codex-rs/app-server-test-client/src/lib.rs @@ -0,0 +1,1057 @@ +use std::collections::VecDeque; +use std::fs; +use std::io::BufRead; +use std::io::BufReader; +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; +use std::process::Child; +use std::process::ChildStdin; +use std::process::ChildStdout; +use std::process::Command; +use std::process::Stdio; +use std::thread; +use std::time::Duration; + +use anyhow::Context; +use anyhow::Result; +use anyhow::bail; +use clap::ArgAction; +use clap::Parser; +use clap::Subcommand; +use codex_app_server_protocol::AddConversationListenerParams; +use codex_app_server_protocol::AddConversationSubscriptionResponse; +use codex_app_server_protocol::AskForApproval; +use codex_app_server_protocol::ClientInfo; +use codex_app_server_protocol::ClientRequest; +use codex_app_server_protocol::CommandExecutionApprovalDecision; +use codex_app_server_protocol::CommandExecutionRequestApprovalParams; +use codex_app_server_protocol::CommandExecutionRequestApprovalResponse; +use codex_app_server_protocol::DynamicToolSpec; +use codex_app_server_protocol::FileChangeApprovalDecision; +use codex_app_server_protocol::FileChangeRequestApprovalParams; +use codex_app_server_protocol::FileChangeRequestApprovalResponse; +use codex_app_server_protocol::GetAccountRateLimitsResponse; +use codex_app_server_protocol::InitializeCapabilities; +use codex_app_server_protocol::InitializeParams; +use codex_app_server_protocol::InitializeResponse; +use codex_app_server_protocol::InputItem; +use codex_app_server_protocol::JSONRPCMessage; +use codex_app_server_protocol::JSONRPCNotification; +use codex_app_server_protocol::JSONRPCRequest; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::LoginChatGptCompleteNotification; +use codex_app_server_protocol::LoginChatGptResponse; +use codex_app_server_protocol::ModelListParams; +use codex_app_server_protocol::ModelListResponse; +use codex_app_server_protocol::NewConversationParams; +use codex_app_server_protocol::NewConversationResponse; +use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::SandboxPolicy; +use codex_app_server_protocol::SendUserMessageParams; +use codex_app_server_protocol::SendUserMessageResponse; +use codex_app_server_protocol::ServerNotification; +use codex_app_server_protocol::ServerRequest; +use codex_app_server_protocol::ThreadStartParams; +use codex_app_server_protocol::ThreadStartResponse; +use codex_app_server_protocol::TurnStartParams; +use codex_app_server_protocol::TurnStartResponse; +use codex_app_server_protocol::TurnStatus; +use codex_app_server_protocol::UserInput as V2UserInput; +use codex_protocol::ThreadId; +use codex_protocol::protocol::Event; +use codex_protocol::protocol::EventMsg; +use serde::Serialize; +use serde::de::DeserializeOwned; +use serde_json::Value; +use uuid::Uuid; + +/// Minimal launcher that initializes the Codex app-server and logs the handshake. +#[derive(Parser)] +#[command(author = "Codex", version, about = "Bootstrap Codex app-server", long_about = None)] +struct Cli { + /// Path to the `codex` CLI binary. + #[arg(long, env = "CODEX_BIN", default_value = "codex")] + codex_bin: PathBuf, + + /// Forwarded to the `codex` CLI as `--config key=value`. Repeatable. + /// + /// Example: + /// `--config 'model_providers.mock.base_url="http://localhost:4010/v2"'` + #[arg( + short = 'c', + long = "config", + value_name = "key=value", + action = ArgAction::Append, + global = true + )] + config_overrides: Vec, + + /// JSON array of dynamic tool specs or a single tool object. + /// Prefix a filename with '@' to read from a file. + /// + /// Example: + /// --dynamic-tools '[{"name":"demo","description":"Demo","inputSchema":{"type":"object"}}]' + /// --dynamic-tools @/path/to/tools.json + #[arg(long, value_name = "json-or-@file", global = true)] + dynamic_tools: Option, + + #[command(subcommand)] + command: CliCommand, +} + +#[derive(Subcommand)] +enum CliCommand { + /// Send a user message through the Codex app-server. + SendMessage { + /// User message to send to Codex. + user_message: String, + }, + /// Send a user message through the app-server V2 thread/turn APIs. + SendMessageV2 { + /// User message to send to Codex. + user_message: String, + }, + /// Start a V2 turn that elicits an ExecCommand approval. + #[command(name = "trigger-cmd-approval")] + TriggerCmdApproval { + /// Optional prompt; defaults to a simple python command. + user_message: Option, + }, + /// Start a V2 turn that elicits an ApplyPatch approval. + #[command(name = "trigger-patch-approval")] + TriggerPatchApproval { + /// Optional prompt; defaults to creating a file via apply_patch. + user_message: Option, + }, + /// Start a V2 turn that should not elicit an ExecCommand approval. + #[command(name = "no-trigger-cmd-approval")] + NoTriggerCmdApproval, + /// Send two sequential V2 turns in the same thread to test follow-up behavior. + SendFollowUpV2 { + /// Initial user message for the first turn. + first_message: String, + /// Follow-up user message for the second turn. + follow_up_message: String, + }, + /// Trigger the ChatGPT login flow and wait for completion. + TestLogin, + /// Fetch the current account rate limits from the Codex app-server. + GetAccountRateLimits, + /// List the available models from the Codex app-server. + #[command(name = "model-list")] + ModelList, +} + +pub fn run() -> Result<()> { + let Cli { + codex_bin, + config_overrides, + dynamic_tools, + command, + } = Cli::parse(); + + let dynamic_tools = parse_dynamic_tools_arg(&dynamic_tools)?; + + match command { + CliCommand::SendMessage { user_message } => { + ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?; + send_message(&codex_bin, &config_overrides, user_message) + } + CliCommand::SendMessageV2 { user_message } => { + send_message_v2(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::TriggerCmdApproval { user_message } => { + trigger_cmd_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::TriggerPatchApproval { user_message } => { + trigger_patch_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::NoTriggerCmdApproval => { + no_trigger_cmd_approval(&codex_bin, &config_overrides, &dynamic_tools) + } + CliCommand::SendFollowUpV2 { + first_message, + follow_up_message, + } => send_follow_up_v2( + &codex_bin, + &config_overrides, + first_message, + follow_up_message, + &dynamic_tools, + ), + CliCommand::TestLogin => { + ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?; + test_login(&codex_bin, &config_overrides) + } + CliCommand::GetAccountRateLimits => { + ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?; + get_account_rate_limits(&codex_bin, &config_overrides) + } + CliCommand::ModelList => { + ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?; + model_list(&codex_bin, &config_overrides) + } + } +} + +fn send_message(codex_bin: &Path, config_overrides: &[String], user_message: String) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let conversation = client.start_thread()?; + println!("< newConversation response: {conversation:?}"); + + let subscription = client.add_conversation_listener(&conversation.conversation_id)?; + println!("< addConversationListener response: {subscription:?}"); + + let send_response = client.send_user_message(&conversation.conversation_id, &user_message)?; + println!("< sendUserMessage response: {send_response:?}"); + + client.stream_conversation(&conversation.conversation_id)?; + + client.remove_thread_listener(subscription.subscription_id)?; + + Ok(()) +} + +pub fn send_message_v2( + codex_bin: &Path, + config_overrides: &[String], + user_message: String, + dynamic_tools: &Option>, +) -> Result<()> { + send_message_v2_with_policies( + codex_bin, + config_overrides, + user_message, + None, + None, + dynamic_tools, + ) +} + +fn trigger_cmd_approval( + codex_bin: &Path, + config_overrides: &[String], + user_message: Option, + dynamic_tools: &Option>, +) -> Result<()> { + let default_prompt = + "Run `touch /tmp/should-trigger-approval` so I can confirm the file exists."; + let message = user_message.unwrap_or_else(|| default_prompt.to_string()); + send_message_v2_with_policies( + codex_bin, + config_overrides, + message, + Some(AskForApproval::OnRequest), + Some(SandboxPolicy::ReadOnly), + dynamic_tools, + ) +} + +fn trigger_patch_approval( + codex_bin: &Path, + config_overrides: &[String], + user_message: Option, + dynamic_tools: &Option>, +) -> Result<()> { + let default_prompt = + "Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch."; + let message = user_message.unwrap_or_else(|| default_prompt.to_string()); + send_message_v2_with_policies( + codex_bin, + config_overrides, + message, + Some(AskForApproval::OnRequest), + Some(SandboxPolicy::ReadOnly), + dynamic_tools, + ) +} + +fn no_trigger_cmd_approval( + codex_bin: &Path, + config_overrides: &[String], + dynamic_tools: &Option>, +) -> Result<()> { + let prompt = "Run `touch should_not_trigger_approval.txt`"; + send_message_v2_with_policies( + codex_bin, + config_overrides, + prompt.to_string(), + None, + None, + dynamic_tools, + ) +} + +fn send_message_v2_with_policies( + codex_bin: &Path, + config_overrides: &[String], + user_message: String, + approval_policy: Option, + sandbox_policy: Option, + dynamic_tools: &Option>, +) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; + println!("< thread/start response: {thread_response:?}"); + let mut turn_params = TurnStartParams { + thread_id: thread_response.thread.id.clone(), + input: vec![V2UserInput::Text { + text: user_message, + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + ..Default::default() + }; + turn_params.approval_policy = approval_policy; + turn_params.sandbox_policy = sandbox_policy; + + let turn_response = client.turn_start(turn_params)?; + println!("< turn/start response: {turn_response:?}"); + + client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?; + + Ok(()) +} + +fn send_follow_up_v2( + codex_bin: &Path, + config_overrides: &[String], + first_message: String, + follow_up_message: String, + dynamic_tools: &Option>, +) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; + println!("< thread/start response: {thread_response:?}"); + + let first_turn_params = TurnStartParams { + thread_id: thread_response.thread.id.clone(), + input: vec![V2UserInput::Text { + text: first_message, + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + ..Default::default() + }; + let first_turn_response = client.turn_start(first_turn_params)?; + println!("< turn/start response (initial): {first_turn_response:?}"); + client.stream_turn(&thread_response.thread.id, &first_turn_response.turn.id)?; + + let follow_up_params = TurnStartParams { + thread_id: thread_response.thread.id.clone(), + input: vec![V2UserInput::Text { + text: follow_up_message, + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + ..Default::default() + }; + let follow_up_response = client.turn_start(follow_up_params)?; + println!("< turn/start response (follow-up): {follow_up_response:?}"); + client.stream_turn(&thread_response.thread.id, &follow_up_response.turn.id)?; + + Ok(()) +} + +fn test_login(codex_bin: &Path, config_overrides: &[String]) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let login_response = client.login_chat_gpt()?; + println!("< loginChatGpt response: {login_response:?}"); + println!( + "Open the following URL in your browser to continue:\n{}", + login_response.auth_url + ); + + let completion = client.wait_for_login_completion(&login_response.login_id)?; + println!("< loginChatGptComplete notification: {completion:?}"); + + if completion.success { + println!("Login succeeded."); + Ok(()) + } else { + bail!( + "login failed: {}", + completion + .error + .as_deref() + .unwrap_or("unknown error from loginChatGptComplete") + ); + } +} + +fn get_account_rate_limits(codex_bin: &Path, config_overrides: &[String]) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let response = client.get_account_rate_limits()?; + println!("< account/rateLimits/read response: {response:?}"); + + Ok(()) +} + +fn model_list(codex_bin: &Path, config_overrides: &[String]) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let response = client.model_list(ModelListParams::default())?; + println!("< model/list response: {response:?}"); + + Ok(()) +} + +fn ensure_dynamic_tools_unused( + dynamic_tools: &Option>, + command: &str, +) -> Result<()> { + if dynamic_tools.is_some() { + bail!( + "dynamic tools are only supported for v2 thread/start; remove --dynamic-tools for {command} or use send-message-v2" + ); + } + Ok(()) +} + +fn parse_dynamic_tools_arg(dynamic_tools: &Option) -> Result>> { + let Some(raw_arg) = dynamic_tools.as_deref() else { + return Ok(None); + }; + + let raw_json = if let Some(path) = raw_arg.strip_prefix('@') { + fs::read_to_string(Path::new(path)) + .with_context(|| format!("read dynamic tools file {path}"))? + } else { + raw_arg.to_string() + }; + + let value: Value = serde_json::from_str(&raw_json).context("parse dynamic tools JSON")?; + let tools = match value { + Value::Array(_) => serde_json::from_value(value).context("decode dynamic tools array")?, + Value::Object(_) => vec![serde_json::from_value(value).context("decode dynamic tool")?], + _ => bail!("dynamic tools JSON must be an object or array"), + }; + + Ok(Some(tools)) +} + +struct CodexClient { + child: Child, + stdin: Option, + stdout: BufReader, + pending_notifications: VecDeque, +} + +impl CodexClient { + fn spawn(codex_bin: &Path, config_overrides: &[String]) -> Result { + let codex_bin_display = codex_bin.display(); + let mut cmd = Command::new(codex_bin); + for override_kv in config_overrides { + cmd.arg("--config").arg(override_kv); + } + let mut codex_app_server = cmd + .arg("app-server") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .with_context(|| format!("failed to start `{codex_bin_display}` app-server"))?; + + let stdin = codex_app_server + .stdin + .take() + .context("codex app-server stdin unavailable")?; + let stdout = codex_app_server + .stdout + .take() + .context("codex app-server stdout unavailable")?; + + Ok(Self { + child: codex_app_server, + stdin: Some(stdin), + stdout: BufReader::new(stdout), + pending_notifications: VecDeque::new(), + }) + } + + fn initialize(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::Initialize { + request_id: request_id.clone(), + params: InitializeParams { + client_info: ClientInfo { + name: "codex-toy-app-server".to_string(), + title: Some("Codex Toy App Server".to_string()), + version: env!("CARGO_PKG_VERSION").to_string(), + }, + capabilities: Some(InitializeCapabilities { + experimental_api: true, + }), + }, + }; + + self.send_request(request, request_id, "initialize") + } + + fn start_thread(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::NewConversation { + request_id: request_id.clone(), + params: NewConversationParams::default(), + }; + + self.send_request(request, request_id, "newConversation") + } + + fn add_conversation_listener( + &mut self, + conversation_id: &ThreadId, + ) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::AddConversationListener { + request_id: request_id.clone(), + params: AddConversationListenerParams { + conversation_id: *conversation_id, + experimental_raw_events: false, + }, + }; + + self.send_request(request, request_id, "addConversationListener") + } + + fn remove_thread_listener(&mut self, subscription_id: Uuid) -> Result<()> { + let request_id = self.request_id(); + let request = ClientRequest::RemoveConversationListener { + request_id: request_id.clone(), + params: codex_app_server_protocol::RemoveConversationListenerParams { subscription_id }, + }; + + self.send_request::( + request, + request_id, + "removeConversationListener", + )?; + + Ok(()) + } + + fn send_user_message( + &mut self, + conversation_id: &ThreadId, + message: &str, + ) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::SendUserMessage { + request_id: request_id.clone(), + params: SendUserMessageParams { + conversation_id: *conversation_id, + items: vec![InputItem::Text { + text: message.to_string(), + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + }, + }; + + self.send_request(request, request_id, "sendUserMessage") + } + + fn thread_start(&mut self, params: ThreadStartParams) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::ThreadStart { + request_id: request_id.clone(), + params, + }; + + self.send_request(request, request_id, "thread/start") + } + + fn turn_start(&mut self, params: TurnStartParams) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::TurnStart { + request_id: request_id.clone(), + params, + }; + + self.send_request(request, request_id, "turn/start") + } + + fn login_chat_gpt(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::LoginChatGpt { + request_id: request_id.clone(), + params: None, + }; + + self.send_request(request, request_id, "loginChatGpt") + } + + fn get_account_rate_limits(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::GetAccountRateLimits { + request_id: request_id.clone(), + params: None, + }; + + self.send_request(request, request_id, "account/rateLimits/read") + } + + fn model_list(&mut self, params: ModelListParams) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::ModelList { + request_id: request_id.clone(), + params, + }; + + self.send_request(request, request_id, "model/list") + } + + fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> { + loop { + let notification = self.next_notification()?; + + if !notification.method.starts_with("codex/event/") { + continue; + } + + if let Some(event) = self.extract_event(notification, conversation_id)? { + match &event.msg { + EventMsg::AgentMessage(event) => { + println!("{}", event.message); + } + EventMsg::AgentMessageDelta(event) => { + print!("{}", event.delta); + std::io::stdout().flush().ok(); + } + EventMsg::TurnComplete(event) => { + println!("\n[task complete: {event:?}]"); + break; + } + EventMsg::TurnAborted(event) => { + println!("\n[turn aborted: {:?}]", event.reason); + break; + } + EventMsg::Error(event) => { + println!("[error] {event:?}"); + } + _ => { + println!("[UNKNOWN EVENT] {:?}", event.msg); + } + } + } + } + + Ok(()) + } + + fn wait_for_login_completion( + &mut self, + expected_login_id: &Uuid, + ) -> Result { + loop { + let notification = self.next_notification()?; + + if let Ok(server_notification) = ServerNotification::try_from(notification) { + match server_notification { + ServerNotification::LoginChatGptComplete(completion) => { + if &completion.login_id == expected_login_id { + return Ok(completion); + } + + println!( + "[ignoring loginChatGptComplete for unexpected login_id: {}]", + completion.login_id + ); + } + ServerNotification::AuthStatusChange(status) => { + println!("< authStatusChange notification: {status:?}"); + } + ServerNotification::AccountRateLimitsUpdated(snapshot) => { + println!("< accountRateLimitsUpdated notification: {snapshot:?}"); + } + ServerNotification::SessionConfigured(_) => { + // SessionConfigured notifications are unrelated to login; skip. + } + _ => {} + } + } + + // Not a server notification (likely a conversation event); keep waiting. + } + } + + fn stream_turn(&mut self, thread_id: &str, turn_id: &str) -> Result<()> { + loop { + let notification = self.next_notification()?; + + let Ok(server_notification) = ServerNotification::try_from(notification) else { + continue; + }; + + match server_notification { + ServerNotification::ThreadStarted(payload) => { + if payload.thread.id == thread_id { + println!("< thread/started notification: {:?}", payload.thread); + } + } + ServerNotification::TurnStarted(payload) => { + if payload.turn.id == turn_id { + println!("< turn/started notification: {:?}", payload.turn.status); + } + } + ServerNotification::AgentMessageDelta(delta) => { + print!("{}", delta.delta); + std::io::stdout().flush().ok(); + } + ServerNotification::CommandExecutionOutputDelta(delta) => { + print!("{}", delta.delta); + std::io::stdout().flush().ok(); + } + ServerNotification::TerminalInteraction(delta) => { + println!("[stdin sent: {}]", delta.stdin); + std::io::stdout().flush().ok(); + } + ServerNotification::ItemStarted(payload) => { + println!("\n< item started: {:?}", payload.item); + } + ServerNotification::ItemCompleted(payload) => { + println!("< item completed: {:?}", payload.item); + } + ServerNotification::TurnCompleted(payload) => { + if payload.turn.id == turn_id { + println!("\n< turn/completed notification: {:?}", payload.turn.status); + if payload.turn.status == TurnStatus::Failed + && let Some(error) = payload.turn.error + { + println!("[turn error] {}", error.message); + } + break; + } + } + ServerNotification::McpToolCallProgress(payload) => { + println!("< MCP tool progress: {}", payload.message); + } + _ => { + println!("[UNKNOWN SERVER NOTIFICATION] {server_notification:?}"); + } + } + } + + Ok(()) + } + + fn extract_event( + &self, + notification: JSONRPCNotification, + conversation_id: &ThreadId, + ) -> Result> { + let params = notification + .params + .context("event notification missing params")?; + + let mut map = match params { + Value::Object(map) => map, + other => bail!("unexpected params shape: {other:?}"), + }; + + let conversation_value = map + .remove("conversationId") + .context("event missing conversationId")?; + let notification_conversation: ThreadId = serde_json::from_value(conversation_value) + .context("conversationId was not a valid UUID")?; + + if ¬ification_conversation != conversation_id { + return Ok(None); + } + + let event_value = Value::Object(map); + let event: Event = + serde_json::from_value(event_value).context("failed to decode event payload")?; + Ok(Some(event)) + } + + fn send_request( + &mut self, + request: ClientRequest, + request_id: RequestId, + method: &str, + ) -> Result + where + T: DeserializeOwned, + { + self.write_request(&request)?; + self.wait_for_response(request_id, method) + } + + fn write_request(&mut self, request: &ClientRequest) -> Result<()> { + let request_json = serde_json::to_string(request)?; + let request_pretty = serde_json::to_string_pretty(request)?; + print_multiline_with_prefix("> ", &request_pretty); + + if let Some(stdin) = self.stdin.as_mut() { + writeln!(stdin, "{request_json}")?; + stdin + .flush() + .context("failed to flush request to codex app-server")?; + } else { + bail!("codex app-server stdin closed"); + } + + Ok(()) + } + + fn wait_for_response(&mut self, request_id: RequestId, method: &str) -> Result + where + T: DeserializeOwned, + { + loop { + let message = self.read_jsonrpc_message()?; + + match message { + JSONRPCMessage::Response(JSONRPCResponse { id, result }) => { + if id == request_id { + return serde_json::from_value(result) + .with_context(|| format!("{method} response missing payload")); + } + } + JSONRPCMessage::Error(err) => { + if err.id == request_id { + bail!("{method} failed: {err:?}"); + } + } + JSONRPCMessage::Notification(notification) => { + self.pending_notifications.push_back(notification); + } + JSONRPCMessage::Request(request) => { + self.handle_server_request(request)?; + } + } + } + } + + fn next_notification(&mut self) -> Result { + if let Some(notification) = self.pending_notifications.pop_front() { + return Ok(notification); + } + + loop { + let message = self.read_jsonrpc_message()?; + + match message { + JSONRPCMessage::Notification(notification) => return Ok(notification), + JSONRPCMessage::Response(_) | JSONRPCMessage::Error(_) => { + // No outstanding requests, so ignore stray responses/errors for now. + continue; + } + JSONRPCMessage::Request(request) => { + self.handle_server_request(request)?; + } + } + } + } + + fn read_jsonrpc_message(&mut self) -> Result { + loop { + let mut response_line = String::new(); + let bytes = self + .stdout + .read_line(&mut response_line) + .context("failed to read from codex app-server")?; + + if bytes == 0 { + bail!("codex app-server closed stdout"); + } + + let trimmed = response_line.trim(); + if trimmed.is_empty() { + continue; + } + + let parsed: Value = + serde_json::from_str(trimmed).context("response was not valid JSON-RPC")?; + let pretty = serde_json::to_string_pretty(&parsed)?; + print_multiline_with_prefix("< ", &pretty); + let message: JSONRPCMessage = serde_json::from_value(parsed) + .context("response was not a valid JSON-RPC message")?; + return Ok(message); + } + } + + fn request_id(&self) -> RequestId { + RequestId::String(Uuid::new_v4().to_string()) + } + + fn handle_server_request(&mut self, request: JSONRPCRequest) -> Result<()> { + let server_request = ServerRequest::try_from(request) + .context("failed to deserialize ServerRequest from JSONRPCRequest")?; + + match server_request { + ServerRequest::CommandExecutionRequestApproval { request_id, params } => { + self.handle_command_execution_request_approval(request_id, params)?; + } + ServerRequest::FileChangeRequestApproval { request_id, params } => { + self.approve_file_change_request(request_id, params)?; + } + other => { + bail!("received unsupported server request: {other:?}"); + } + } + + Ok(()) + } + + fn handle_command_execution_request_approval( + &mut self, + request_id: RequestId, + params: CommandExecutionRequestApprovalParams, + ) -> Result<()> { + let CommandExecutionRequestApprovalParams { + thread_id, + turn_id, + item_id, + reason, + command, + cwd, + command_actions, + proposed_execpolicy_amendment, + } = params; + + println!( + "\n< commandExecution approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" + ); + if let Some(reason) = reason.as_deref() { + println!("< reason: {reason}"); + } + if let Some(command) = command.as_deref() { + println!("< command: {command}"); + } + if let Some(cwd) = cwd.as_ref() { + println!("< cwd: {}", cwd.display()); + } + if let Some(command_actions) = command_actions.as_ref() + && !command_actions.is_empty() + { + println!("< command actions: {command_actions:?}"); + } + if let Some(execpolicy_amendment) = proposed_execpolicy_amendment.as_ref() { + println!("< proposed execpolicy amendment: {execpolicy_amendment:?}"); + } + + let response = CommandExecutionRequestApprovalResponse { + decision: CommandExecutionApprovalDecision::Accept, + }; + self.send_server_request_response(request_id, &response)?; + println!("< approved commandExecution request for item {item_id}"); + Ok(()) + } + + fn approve_file_change_request( + &mut self, + request_id: RequestId, + params: FileChangeRequestApprovalParams, + ) -> Result<()> { + let FileChangeRequestApprovalParams { + thread_id, + turn_id, + item_id, + reason, + grant_root, + } = params; + + println!( + "\n< fileChange approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" + ); + if let Some(reason) = reason.as_deref() { + println!("< reason: {reason}"); + } + if let Some(grant_root) = grant_root.as_deref() { + println!("< grant root: {}", grant_root.display()); + } + + let response = FileChangeRequestApprovalResponse { + decision: FileChangeApprovalDecision::Accept, + }; + self.send_server_request_response(request_id, &response)?; + println!("< approved fileChange request for item {item_id}"); + Ok(()) + } + + fn send_server_request_response(&mut self, request_id: RequestId, response: &T) -> Result<()> + where + T: Serialize, + { + let message = JSONRPCMessage::Response(JSONRPCResponse { + id: request_id, + result: serde_json::to_value(response)?, + }); + self.write_jsonrpc_message(message) + } + + fn write_jsonrpc_message(&mut self, message: JSONRPCMessage) -> Result<()> { + let payload = serde_json::to_string(&message)?; + let pretty = serde_json::to_string_pretty(&message)?; + print_multiline_with_prefix("> ", &pretty); + + if let Some(stdin) = self.stdin.as_mut() { + writeln!(stdin, "{payload}")?; + stdin + .flush() + .context("failed to flush response to codex app-server")?; + return Ok(()); + } + + bail!("codex app-server stdin closed") + } +} + +fn print_multiline_with_prefix(prefix: &str, payload: &str) { + for line in payload.lines() { + println!("{prefix}{line}"); + } +} + +impl Drop for CodexClient { + fn drop(&mut self) { + let _ = self.stdin.take(); + + if let Ok(Some(status)) = self.child.try_wait() { + println!("[codex app-server exited: {status}]"); + return; + } + + thread::sleep(Duration::from_millis(100)); + + if let Ok(Some(status)) = self.child.try_wait() { + println!("[codex app-server exited: {status}]"); + return; + } + + let _ = self.child.kill(); + let _ = self.child.wait(); + } +} diff --git a/codex-rs/app-server-test-client/src/main.rs b/codex-rs/app-server-test-client/src/main.rs index 6d3fc06d81..a4da2e4026 100644 --- a/codex-rs/app-server-test-client/src/main.rs +++ b/codex-rs/app-server-test-client/src/main.rs @@ -1,1061 +1,5 @@ -use std::collections::VecDeque; -use std::fs; -use std::io::BufRead; -use std::io::BufReader; -use std::io::Write; -use std::path::Path; -use std::process::Child; -use std::process::ChildStdin; -use std::process::ChildStdout; -use std::process::Command; -use std::process::Stdio; -use std::thread; -use std::time::Duration; - -use anyhow::Context; use anyhow::Result; -use anyhow::bail; -use clap::ArgAction; -use clap::Parser; -use clap::Subcommand; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; -use codex_app_server_protocol::AskForApproval; -use codex_app_server_protocol::ClientInfo; -use codex_app_server_protocol::ClientRequest; -use codex_app_server_protocol::CommandExecutionApprovalDecision; -use codex_app_server_protocol::CommandExecutionRequestApprovalParams; -use codex_app_server_protocol::CommandExecutionRequestApprovalResponse; -use codex_app_server_protocol::DynamicToolSpec; -use codex_app_server_protocol::FileChangeApprovalDecision; -use codex_app_server_protocol::FileChangeRequestApprovalParams; -use codex_app_server_protocol::FileChangeRequestApprovalResponse; -use codex_app_server_protocol::GetAccountRateLimitsResponse; -use codex_app_server_protocol::InitializeCapabilities; -use codex_app_server_protocol::InitializeParams; -use codex_app_server_protocol::InitializeResponse; -use codex_app_server_protocol::InputItem; -use codex_app_server_protocol::JSONRPCMessage; -use codex_app_server_protocol::JSONRPCNotification; -use codex_app_server_protocol::JSONRPCRequest; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginChatGptCompleteNotification; -use codex_app_server_protocol::LoginChatGptResponse; -use codex_app_server_protocol::ModelListParams; -use codex_app_server_protocol::ModelListResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SandboxPolicy; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use codex_app_server_protocol::ServerNotification; -use codex_app_server_protocol::ServerRequest; -use codex_app_server_protocol::ThreadStartParams; -use codex_app_server_protocol::ThreadStartResponse; -use codex_app_server_protocol::TurnStartParams; -use codex_app_server_protocol::TurnStartResponse; -use codex_app_server_protocol::TurnStatus; -use codex_app_server_protocol::UserInput as V2UserInput; -use codex_protocol::ThreadId; -use codex_protocol::protocol::Event; -use codex_protocol::protocol::EventMsg; -use serde::Serialize; -use serde::de::DeserializeOwned; -use serde_json::Value; -use uuid::Uuid; - -/// Minimal launcher that initializes the Codex app-server and logs the handshake. -#[derive(Parser)] -#[command(author = "Codex", version, about = "Bootstrap Codex app-server", long_about = None)] -struct Cli { - /// Path to the `codex` CLI binary. - #[arg(long, env = "CODEX_BIN", default_value = "codex")] - codex_bin: String, - - /// Forwarded to the `codex` CLI as `--config key=value`. Repeatable. - /// - /// Example: - /// `--config 'model_providers.mock.base_url="http://localhost:4010/v2"'` - #[arg( - short = 'c', - long = "config", - value_name = "key=value", - action = ArgAction::Append, - global = true - )] - config_overrides: Vec, - - /// JSON array of dynamic tool specs or a single tool object. - /// Prefix a filename with '@' to read from a file. - /// - /// Example: - /// --dynamic-tools '[{"name":"demo","description":"Demo","inputSchema":{"type":"object"}}]' - /// --dynamic-tools @/path/to/tools.json - #[arg(long, value_name = "json-or-@file", global = true)] - dynamic_tools: Option, - - #[command(subcommand)] - command: CliCommand, -} - -#[derive(Subcommand)] -enum CliCommand { - /// Send a user message through the Codex app-server. - SendMessage { - /// User message to send to Codex. - #[arg()] - user_message: String, - }, - /// Send a user message through the app-server V2 thread/turn APIs. - SendMessageV2 { - /// User message to send to Codex. - #[arg()] - user_message: String, - }, - /// Start a V2 turn that elicits an ExecCommand approval. - #[command(name = "trigger-cmd-approval")] - TriggerCmdApproval { - /// Optional prompt; defaults to a simple python command. - #[arg()] - user_message: Option, - }, - /// Start a V2 turn that elicits an ApplyPatch approval. - #[command(name = "trigger-patch-approval")] - TriggerPatchApproval { - /// Optional prompt; defaults to creating a file via apply_patch. - #[arg()] - user_message: Option, - }, - /// Start a V2 turn that should not elicit an ExecCommand approval. - #[command(name = "no-trigger-cmd-approval")] - NoTriggerCmdApproval, - /// Send two sequential V2 turns in the same thread to test follow-up behavior. - SendFollowUpV2 { - /// Initial user message for the first turn. - #[arg()] - first_message: String, - /// Follow-up user message for the second turn. - #[arg()] - follow_up_message: String, - }, - /// Trigger the ChatGPT login flow and wait for completion. - TestLogin, - /// Fetch the current account rate limits from the Codex app-server. - GetAccountRateLimits, - /// List the available models from the Codex app-server. - #[command(name = "model-list")] - ModelList, -} fn main() -> Result<()> { - let Cli { - codex_bin, - config_overrides, - dynamic_tools, - command, - } = Cli::parse(); - - let dynamic_tools = parse_dynamic_tools_arg(&dynamic_tools)?; - - match command { - CliCommand::SendMessage { user_message } => { - ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?; - send_message(&codex_bin, &config_overrides, user_message) - } - CliCommand::SendMessageV2 { user_message } => { - send_message_v2(&codex_bin, &config_overrides, user_message, &dynamic_tools) - } - CliCommand::TriggerCmdApproval { user_message } => { - trigger_cmd_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) - } - CliCommand::TriggerPatchApproval { user_message } => { - trigger_patch_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) - } - CliCommand::NoTriggerCmdApproval => { - no_trigger_cmd_approval(&codex_bin, &config_overrides, &dynamic_tools) - } - CliCommand::SendFollowUpV2 { - first_message, - follow_up_message, - } => send_follow_up_v2( - &codex_bin, - &config_overrides, - first_message, - follow_up_message, - &dynamic_tools, - ), - CliCommand::TestLogin => { - ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?; - test_login(&codex_bin, &config_overrides) - } - CliCommand::GetAccountRateLimits => { - ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?; - get_account_rate_limits(&codex_bin, &config_overrides) - } - CliCommand::ModelList => { - ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?; - model_list(&codex_bin, &config_overrides) - } - } -} - -fn send_message(codex_bin: &str, config_overrides: &[String], user_message: String) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let conversation = client.start_thread()?; - println!("< newConversation response: {conversation:?}"); - - let subscription = client.add_conversation_listener(&conversation.conversation_id)?; - println!("< addConversationListener response: {subscription:?}"); - - let send_response = client.send_user_message(&conversation.conversation_id, &user_message)?; - println!("< sendUserMessage response: {send_response:?}"); - - client.stream_conversation(&conversation.conversation_id)?; - - client.remove_thread_listener(subscription.subscription_id)?; - - Ok(()) -} - -fn send_message_v2( - codex_bin: &str, - config_overrides: &[String], - user_message: String, - dynamic_tools: &Option>, -) -> Result<()> { - send_message_v2_with_policies( - codex_bin, - config_overrides, - user_message, - None, - None, - dynamic_tools, - ) -} - -fn trigger_cmd_approval( - codex_bin: &str, - config_overrides: &[String], - user_message: Option, - dynamic_tools: &Option>, -) -> Result<()> { - let default_prompt = - "Run `touch /tmp/should-trigger-approval` so I can confirm the file exists."; - let message = user_message.unwrap_or_else(|| default_prompt.to_string()); - send_message_v2_with_policies( - codex_bin, - config_overrides, - message, - Some(AskForApproval::OnRequest), - Some(SandboxPolicy::ReadOnly), - dynamic_tools, - ) -} - -fn trigger_patch_approval( - codex_bin: &str, - config_overrides: &[String], - user_message: Option, - dynamic_tools: &Option>, -) -> Result<()> { - let default_prompt = - "Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch."; - let message = user_message.unwrap_or_else(|| default_prompt.to_string()); - send_message_v2_with_policies( - codex_bin, - config_overrides, - message, - Some(AskForApproval::OnRequest), - Some(SandboxPolicy::ReadOnly), - dynamic_tools, - ) -} - -fn no_trigger_cmd_approval( - codex_bin: &str, - config_overrides: &[String], - dynamic_tools: &Option>, -) -> Result<()> { - let prompt = "Run `touch should_not_trigger_approval.txt`"; - send_message_v2_with_policies( - codex_bin, - config_overrides, - prompt.to_string(), - None, - None, - dynamic_tools, - ) -} - -fn send_message_v2_with_policies( - codex_bin: &str, - config_overrides: &[String], - user_message: String, - approval_policy: Option, - sandbox_policy: Option, - dynamic_tools: &Option>, -) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let thread_response = client.thread_start(ThreadStartParams { - dynamic_tools: dynamic_tools.clone(), - ..Default::default() - })?; - println!("< thread/start response: {thread_response:?}"); - let mut turn_params = TurnStartParams { - thread_id: thread_response.thread.id.clone(), - input: vec![V2UserInput::Text { - text: user_message, - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - ..Default::default() - }; - turn_params.approval_policy = approval_policy; - turn_params.sandbox_policy = sandbox_policy; - - let turn_response = client.turn_start(turn_params)?; - println!("< turn/start response: {turn_response:?}"); - - client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?; - - Ok(()) -} - -fn send_follow_up_v2( - codex_bin: &str, - config_overrides: &[String], - first_message: String, - follow_up_message: String, - dynamic_tools: &Option>, -) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let thread_response = client.thread_start(ThreadStartParams { - dynamic_tools: dynamic_tools.clone(), - ..Default::default() - })?; - println!("< thread/start response: {thread_response:?}"); - - let first_turn_params = TurnStartParams { - thread_id: thread_response.thread.id.clone(), - input: vec![V2UserInput::Text { - text: first_message, - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - ..Default::default() - }; - let first_turn_response = client.turn_start(first_turn_params)?; - println!("< turn/start response (initial): {first_turn_response:?}"); - client.stream_turn(&thread_response.thread.id, &first_turn_response.turn.id)?; - - let follow_up_params = TurnStartParams { - thread_id: thread_response.thread.id.clone(), - input: vec![V2UserInput::Text { - text: follow_up_message, - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - ..Default::default() - }; - let follow_up_response = client.turn_start(follow_up_params)?; - println!("< turn/start response (follow-up): {follow_up_response:?}"); - client.stream_turn(&thread_response.thread.id, &follow_up_response.turn.id)?; - - Ok(()) -} - -fn test_login(codex_bin: &str, config_overrides: &[String]) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let login_response = client.login_chat_gpt()?; - println!("< loginChatGpt response: {login_response:?}"); - println!( - "Open the following URL in your browser to continue:\n{}", - login_response.auth_url - ); - - let completion = client.wait_for_login_completion(&login_response.login_id)?; - println!("< loginChatGptComplete notification: {completion:?}"); - - if completion.success { - println!("Login succeeded."); - Ok(()) - } else { - bail!( - "login failed: {}", - completion - .error - .as_deref() - .unwrap_or("unknown error from loginChatGptComplete") - ); - } -} - -fn get_account_rate_limits(codex_bin: &str, config_overrides: &[String]) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let response = client.get_account_rate_limits()?; - println!("< account/rateLimits/read response: {response:?}"); - - Ok(()) -} - -fn model_list(codex_bin: &str, config_overrides: &[String]) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let response = client.model_list(ModelListParams::default())?; - println!("< model/list response: {response:?}"); - - Ok(()) -} - -fn ensure_dynamic_tools_unused( - dynamic_tools: &Option>, - command: &str, -) -> Result<()> { - if dynamic_tools.is_some() { - bail!( - "dynamic tools are only supported for v2 thread/start; remove --dynamic-tools for {command} or use send-message-v2" - ); - } - Ok(()) -} - -fn parse_dynamic_tools_arg(dynamic_tools: &Option) -> Result>> { - let Some(raw_arg) = dynamic_tools.as_deref() else { - return Ok(None); - }; - - let raw_json = if let Some(path) = raw_arg.strip_prefix('@') { - fs::read_to_string(Path::new(path)) - .with_context(|| format!("read dynamic tools file {path}"))? - } else { - raw_arg.to_string() - }; - - let value: Value = serde_json::from_str(&raw_json).context("parse dynamic tools JSON")?; - let tools = match value { - Value::Array(_) => serde_json::from_value(value).context("decode dynamic tools array")?, - Value::Object(_) => vec![serde_json::from_value(value).context("decode dynamic tool")?], - _ => bail!("dynamic tools JSON must be an object or array"), - }; - - Ok(Some(tools)) -} - -struct CodexClient { - child: Child, - stdin: Option, - stdout: BufReader, - pending_notifications: VecDeque, -} - -impl CodexClient { - fn spawn(codex_bin: &str, config_overrides: &[String]) -> Result { - let mut cmd = Command::new(codex_bin); - for override_kv in config_overrides { - cmd.arg("--config").arg(override_kv); - } - let mut codex_app_server = cmd - .arg("app-server") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - .with_context(|| format!("failed to start `{codex_bin}` app-server"))?; - - let stdin = codex_app_server - .stdin - .take() - .context("codex app-server stdin unavailable")?; - let stdout = codex_app_server - .stdout - .take() - .context("codex app-server stdout unavailable")?; - - Ok(Self { - child: codex_app_server, - stdin: Some(stdin), - stdout: BufReader::new(stdout), - pending_notifications: VecDeque::new(), - }) - } - - fn initialize(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::Initialize { - request_id: request_id.clone(), - params: InitializeParams { - client_info: ClientInfo { - name: "codex-toy-app-server".to_string(), - title: Some("Codex Toy App Server".to_string()), - version: env!("CARGO_PKG_VERSION").to_string(), - }, - capabilities: Some(InitializeCapabilities { - experimental_api: true, - }), - }, - }; - - self.send_request(request, request_id, "initialize") - } - - fn start_thread(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::NewConversation { - request_id: request_id.clone(), - params: NewConversationParams::default(), - }; - - self.send_request(request, request_id, "newConversation") - } - - fn add_conversation_listener( - &mut self, - conversation_id: &ThreadId, - ) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::AddConversationListener { - request_id: request_id.clone(), - params: AddConversationListenerParams { - conversation_id: *conversation_id, - experimental_raw_events: false, - }, - }; - - self.send_request(request, request_id, "addConversationListener") - } - - fn remove_thread_listener(&mut self, subscription_id: Uuid) -> Result<()> { - let request_id = self.request_id(); - let request = ClientRequest::RemoveConversationListener { - request_id: request_id.clone(), - params: codex_app_server_protocol::RemoveConversationListenerParams { subscription_id }, - }; - - self.send_request::( - request, - request_id, - "removeConversationListener", - )?; - - Ok(()) - } - - fn send_user_message( - &mut self, - conversation_id: &ThreadId, - message: &str, - ) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::SendUserMessage { - request_id: request_id.clone(), - params: SendUserMessageParams { - conversation_id: *conversation_id, - items: vec![InputItem::Text { - text: message.to_string(), - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - }, - }; - - self.send_request(request, request_id, "sendUserMessage") - } - - fn thread_start(&mut self, params: ThreadStartParams) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::ThreadStart { - request_id: request_id.clone(), - params, - }; - - self.send_request(request, request_id, "thread/start") - } - - fn turn_start(&mut self, params: TurnStartParams) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::TurnStart { - request_id: request_id.clone(), - params, - }; - - self.send_request(request, request_id, "turn/start") - } - - fn login_chat_gpt(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::LoginChatGpt { - request_id: request_id.clone(), - params: None, - }; - - self.send_request(request, request_id, "loginChatGpt") - } - - fn get_account_rate_limits(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::GetAccountRateLimits { - request_id: request_id.clone(), - params: None, - }; - - self.send_request(request, request_id, "account/rateLimits/read") - } - - fn model_list(&mut self, params: ModelListParams) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::ModelList { - request_id: request_id.clone(), - params, - }; - - self.send_request(request, request_id, "model/list") - } - - fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> { - loop { - let notification = self.next_notification()?; - - if !notification.method.starts_with("codex/event/") { - continue; - } - - if let Some(event) = self.extract_event(notification, conversation_id)? { - match &event.msg { - EventMsg::AgentMessage(event) => { - println!("{}", event.message); - } - EventMsg::AgentMessageDelta(event) => { - print!("{}", event.delta); - std::io::stdout().flush().ok(); - } - EventMsg::TurnComplete(event) => { - println!("\n[task complete: {event:?}]"); - break; - } - EventMsg::TurnAborted(event) => { - println!("\n[turn aborted: {:?}]", event.reason); - break; - } - EventMsg::Error(event) => { - println!("[error] {event:?}"); - } - _ => { - println!("[UNKNOWN EVENT] {:?}", event.msg); - } - } - } - } - - Ok(()) - } - - fn wait_for_login_completion( - &mut self, - expected_login_id: &Uuid, - ) -> Result { - loop { - let notification = self.next_notification()?; - - if let Ok(server_notification) = ServerNotification::try_from(notification) { - match server_notification { - ServerNotification::LoginChatGptComplete(completion) => { - if &completion.login_id == expected_login_id { - return Ok(completion); - } - - println!( - "[ignoring loginChatGptComplete for unexpected login_id: {}]", - completion.login_id - ); - } - ServerNotification::AuthStatusChange(status) => { - println!("< authStatusChange notification: {status:?}"); - } - ServerNotification::AccountRateLimitsUpdated(snapshot) => { - println!("< accountRateLimitsUpdated notification: {snapshot:?}"); - } - ServerNotification::SessionConfigured(_) => { - // SessionConfigured notifications are unrelated to login; skip. - } - _ => {} - } - } - - // Not a server notification (likely a conversation event); keep waiting. - } - } - - fn stream_turn(&mut self, thread_id: &str, turn_id: &str) -> Result<()> { - loop { - let notification = self.next_notification()?; - - let Ok(server_notification) = ServerNotification::try_from(notification) else { - continue; - }; - - match server_notification { - ServerNotification::ThreadStarted(payload) => { - if payload.thread.id == thread_id { - println!("< thread/started notification: {:?}", payload.thread); - } - } - ServerNotification::TurnStarted(payload) => { - if payload.turn.id == turn_id { - println!("< turn/started notification: {:?}", payload.turn.status); - } - } - ServerNotification::AgentMessageDelta(delta) => { - print!("{}", delta.delta); - std::io::stdout().flush().ok(); - } - ServerNotification::CommandExecutionOutputDelta(delta) => { - print!("{}", delta.delta); - std::io::stdout().flush().ok(); - } - ServerNotification::TerminalInteraction(delta) => { - println!("[stdin sent: {}]", delta.stdin); - std::io::stdout().flush().ok(); - } - ServerNotification::ItemStarted(payload) => { - println!("\n< item started: {:?}", payload.item); - } - ServerNotification::ItemCompleted(payload) => { - println!("< item completed: {:?}", payload.item); - } - ServerNotification::TurnCompleted(payload) => { - if payload.turn.id == turn_id { - println!("\n< turn/completed notification: {:?}", payload.turn.status); - if payload.turn.status == TurnStatus::Failed - && let Some(error) = payload.turn.error - { - println!("[turn error] {}", error.message); - } - break; - } - } - ServerNotification::McpToolCallProgress(payload) => { - println!("< MCP tool progress: {}", payload.message); - } - _ => { - println!("[UNKNOWN SERVER NOTIFICATION] {server_notification:?}"); - } - } - } - - Ok(()) - } - - fn extract_event( - &self, - notification: JSONRPCNotification, - conversation_id: &ThreadId, - ) -> Result> { - let params = notification - .params - .context("event notification missing params")?; - - let mut map = match params { - Value::Object(map) => map, - other => bail!("unexpected params shape: {other:?}"), - }; - - let conversation_value = map - .remove("conversationId") - .context("event missing conversationId")?; - let notification_conversation: ThreadId = serde_json::from_value(conversation_value) - .context("conversationId was not a valid UUID")?; - - if ¬ification_conversation != conversation_id { - return Ok(None); - } - - let event_value = Value::Object(map); - let event: Event = - serde_json::from_value(event_value).context("failed to decode event payload")?; - Ok(Some(event)) - } - - fn send_request( - &mut self, - request: ClientRequest, - request_id: RequestId, - method: &str, - ) -> Result - where - T: DeserializeOwned, - { - self.write_request(&request)?; - self.wait_for_response(request_id, method) - } - - fn write_request(&mut self, request: &ClientRequest) -> Result<()> { - let request_json = serde_json::to_string(request)?; - let request_pretty = serde_json::to_string_pretty(request)?; - print_multiline_with_prefix("> ", &request_pretty); - - if let Some(stdin) = self.stdin.as_mut() { - writeln!(stdin, "{request_json}")?; - stdin - .flush() - .context("failed to flush request to codex app-server")?; - } else { - bail!("codex app-server stdin closed"); - } - - Ok(()) - } - - fn wait_for_response(&mut self, request_id: RequestId, method: &str) -> Result - where - T: DeserializeOwned, - { - loop { - let message = self.read_jsonrpc_message()?; - - match message { - JSONRPCMessage::Response(JSONRPCResponse { id, result }) => { - if id == request_id { - return serde_json::from_value(result) - .with_context(|| format!("{method} response missing payload")); - } - } - JSONRPCMessage::Error(err) => { - if err.id == request_id { - bail!("{method} failed: {err:?}"); - } - } - JSONRPCMessage::Notification(notification) => { - self.pending_notifications.push_back(notification); - } - JSONRPCMessage::Request(request) => { - self.handle_server_request(request)?; - } - } - } - } - - fn next_notification(&mut self) -> Result { - if let Some(notification) = self.pending_notifications.pop_front() { - return Ok(notification); - } - - loop { - let message = self.read_jsonrpc_message()?; - - match message { - JSONRPCMessage::Notification(notification) => return Ok(notification), - JSONRPCMessage::Response(_) | JSONRPCMessage::Error(_) => { - // No outstanding requests, so ignore stray responses/errors for now. - continue; - } - JSONRPCMessage::Request(request) => { - self.handle_server_request(request)?; - } - } - } - } - - fn read_jsonrpc_message(&mut self) -> Result { - loop { - let mut response_line = String::new(); - let bytes = self - .stdout - .read_line(&mut response_line) - .context("failed to read from codex app-server")?; - - if bytes == 0 { - bail!("codex app-server closed stdout"); - } - - let trimmed = response_line.trim(); - if trimmed.is_empty() { - continue; - } - - let parsed: Value = - serde_json::from_str(trimmed).context("response was not valid JSON-RPC")?; - let pretty = serde_json::to_string_pretty(&parsed)?; - print_multiline_with_prefix("< ", &pretty); - let message: JSONRPCMessage = serde_json::from_value(parsed) - .context("response was not a valid JSON-RPC message")?; - return Ok(message); - } - } - - fn request_id(&self) -> RequestId { - RequestId::String(Uuid::new_v4().to_string()) - } - - fn handle_server_request(&mut self, request: JSONRPCRequest) -> Result<()> { - let server_request = ServerRequest::try_from(request) - .context("failed to deserialize ServerRequest from JSONRPCRequest")?; - - match server_request { - ServerRequest::CommandExecutionRequestApproval { request_id, params } => { - self.handle_command_execution_request_approval(request_id, params)?; - } - ServerRequest::FileChangeRequestApproval { request_id, params } => { - self.approve_file_change_request(request_id, params)?; - } - other => { - bail!("received unsupported server request: {other:?}"); - } - } - - Ok(()) - } - - fn handle_command_execution_request_approval( - &mut self, - request_id: RequestId, - params: CommandExecutionRequestApprovalParams, - ) -> Result<()> { - let CommandExecutionRequestApprovalParams { - thread_id, - turn_id, - item_id, - reason, - command, - cwd, - command_actions, - proposed_execpolicy_amendment, - } = params; - - println!( - "\n< commandExecution approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" - ); - if let Some(reason) = reason.as_deref() { - println!("< reason: {reason}"); - } - if let Some(command) = command.as_deref() { - println!("< command: {command}"); - } - if let Some(cwd) = cwd.as_ref() { - println!("< cwd: {}", cwd.display()); - } - if let Some(command_actions) = command_actions.as_ref() - && !command_actions.is_empty() - { - println!("< command actions: {command_actions:?}"); - } - if let Some(execpolicy_amendment) = proposed_execpolicy_amendment.as_ref() { - println!("< proposed execpolicy amendment: {execpolicy_amendment:?}"); - } - - let response = CommandExecutionRequestApprovalResponse { - decision: CommandExecutionApprovalDecision::Accept, - }; - self.send_server_request_response(request_id, &response)?; - println!("< approved commandExecution request for item {item_id}"); - Ok(()) - } - - fn approve_file_change_request( - &mut self, - request_id: RequestId, - params: FileChangeRequestApprovalParams, - ) -> Result<()> { - let FileChangeRequestApprovalParams { - thread_id, - turn_id, - item_id, - reason, - grant_root, - } = params; - - println!( - "\n< fileChange approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" - ); - if let Some(reason) = reason.as_deref() { - println!("< reason: {reason}"); - } - if let Some(grant_root) = grant_root.as_deref() { - println!("< grant root: {}", grant_root.display()); - } - - let response = FileChangeRequestApprovalResponse { - decision: FileChangeApprovalDecision::Accept, - }; - self.send_server_request_response(request_id, &response)?; - println!("< approved fileChange request for item {item_id}"); - Ok(()) - } - - fn send_server_request_response(&mut self, request_id: RequestId, response: &T) -> Result<()> - where - T: Serialize, - { - let message = JSONRPCMessage::Response(JSONRPCResponse { - id: request_id, - result: serde_json::to_value(response)?, - }); - self.write_jsonrpc_message(message) - } - - fn write_jsonrpc_message(&mut self, message: JSONRPCMessage) -> Result<()> { - let payload = serde_json::to_string(&message)?; - let pretty = serde_json::to_string_pretty(&message)?; - print_multiline_with_prefix("> ", &pretty); - - if let Some(stdin) = self.stdin.as_mut() { - writeln!(stdin, "{payload}")?; - stdin - .flush() - .context("failed to flush response to codex app-server")?; - return Ok(()); - } - - bail!("codex app-server stdin closed") - } -} - -fn print_multiline_with_prefix(prefix: &str, payload: &str) { - for line in payload.lines() { - println!("{prefix}{line}"); - } -} - -impl Drop for CodexClient { - fn drop(&mut self) { - let _ = self.stdin.take(); - - if let Ok(Some(status)) = self.child.try_wait() { - println!("[codex app-server exited: {status}]"); - return; - } - - thread::sleep(Duration::from_millis(100)); - - if let Ok(Some(status)) = self.child.try_wait() { - println!("[codex app-server exited: {status}]"); - return; - } - - let _ = self.child.kill(); - let _ = self.child.wait(); - } + codex_app_server_test_client::run() } diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index cfcfce88f9..77344df477 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -21,6 +21,7 @@ clap = { workspace = true, features = ["derive"] } clap_complete = { workspace = true } codex-app-server = { workspace = true } codex-app-server-protocol = { workspace = true } +codex-app-server-test-client = { workspace = true } codex-arg0 = { workspace = true } codex-chatgpt = { workspace = true } codex-cloud-tasks = { path = "../cloud-tasks" } diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 022d6bbdf5..defc063eb6 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -110,9 +110,11 @@ enum Subcommand { Completion(CompletionCommand), /// Run commands within a Codex-provided sandbox. - #[clap(visible_alias = "debug")] Sandbox(SandboxArgs), + /// Debugging tools. + Debug(DebugCommand), + /// Execpolicy tooling. #[clap(hide = true)] Execpolicy(ExecpolicyCommand), @@ -150,6 +152,36 @@ struct CompletionCommand { shell: Shell, } +#[derive(Debug, Parser)] +struct DebugCommand { + #[command(subcommand)] + subcommand: DebugSubcommand, +} + +#[derive(Debug, clap::Subcommand)] +enum DebugSubcommand { + /// Tooling: helps debug the app server. + AppServer(DebugAppServerCommand), +} + +#[derive(Debug, Parser)] +struct DebugAppServerCommand { + #[command(subcommand)] + subcommand: DebugAppServerSubcommand, +} + +#[derive(Debug, clap::Subcommand)] +enum DebugAppServerSubcommand { + // Send message to app server V2. + SendMessageV2(DebugAppServerSendMessageV2Command), +} + +#[derive(Debug, Parser)] +struct DebugAppServerSendMessageV2Command { + #[arg(value_name = "USER_MESSAGE", required = true)] + user_message: String, +} + #[derive(Debug, Parser)] struct ResumeCommand { /// Conversation/session id (UUID) or thread name. UUIDs take precedence if it parses. @@ -425,6 +457,15 @@ fn run_execpolicycheck(cmd: ExecPolicyCheckCommand) -> anyhow::Result<()> { cmd.run() } +fn run_debug_app_server_command(cmd: DebugAppServerCommand) -> anyhow::Result<()> { + match cmd.subcommand { + DebugAppServerSubcommand::SendMessageV2(cmd) => { + let codex_bin = std::env::current_exe()?; + codex_app_server_test_client::send_message_v2(&codex_bin, &[], cmd.user_message, &None) + } + } +} + #[derive(Debug, Default, Parser, Clone)] struct FeatureToggles { /// Enable a feature (repeatable). Equivalent to `-c features.=true`. @@ -693,6 +734,11 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() .await?; } }, + Some(Subcommand::Debug(DebugCommand { subcommand })) => match subcommand { + DebugSubcommand::AppServer(cmd) => { + run_debug_app_server_command(cmd)?; + } + }, Some(Subcommand::Execpolicy(ExecpolicyCommand { sub })) => match sub { ExecpolicySubcommand::Check(cmd) => run_execpolicycheck(cmd)?, }, From a9eb766f33953d651a0f01670d893a4cf5da3763 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Tue, 3 Feb 2026 16:17:06 -0800 Subject: [PATCH 128/139] tui: make Esc clear request_user_input notes while notes are shown (#10569) ## Summary This PR updates the `request_user_input` TUI overlay so `Esc` is context-aware: - When notes are visible for an option question, `Esc` now clears notes and exits notes mode. - When notes are not visible (normal option selection UI), `Esc` still interrupts as before. It also updates footer guidance text to match behavior. ## Changes - Added a shared notes-clear path for option questions: - `Tab` and `Esc` now both clear notes and return focus to options when notes are visible. - Updated footer hint text in notes-visible state: - from: `tab to clear notes | ... | esc to interrupt` - to: `tab or esc to clear notes | ...` - Hid `esc to interrupt` hint while notes are visible for option questions. - Kept `esc to interrupt` visible and functional in normal option-selection mode. - Updated tests to assert the new `Esc` behavior in notes mode. - Updated snapshot output for the notes-visible footer row. - Updated docs in `docs/tui-request-user-input.md` to reflect mode-specific `Esc` behavior. --- .../src/bottom_pane/request_user_input/mod.rs | 66 +++++++++++++------ ...uest_user_input_options_notes_visible.snap | 3 +- docs/tui-request-user-input.md | 4 +- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs index 59807a8a4c..9542772446 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs +++ b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs @@ -427,8 +427,8 @@ impl RequestUserInputOverlay { if self.selected_option_index().is_some() && !notes_visible { tips.push(FooterTip::highlighted("tab to add notes")); } - if self.selected_option_index().is_some() && notes_visible && self.focus_is_notes() { - tips.push(FooterTip::new("tab to clear notes")); + if self.selected_option_index().is_some() && notes_visible { + tips.push(FooterTip::new("tab or esc to clear notes")); } } @@ -449,7 +449,9 @@ impl RequestUserInputOverlay { tips.push(FooterTip::new("ctrl + n next question")); } } - tips.push(FooterTip::new("esc to interrupt")); + if !(self.has_options() && notes_visible) { + tips.push(FooterTip::new("esc to interrupt")); + } tips } @@ -663,6 +665,23 @@ impl RequestUserInputOverlay { self.sync_composer_placeholder(); } + fn clear_notes_and_focus_options(&mut self) { + if !self.has_options() { + return; + } + if let Some(answer) = self.current_answer_mut() { + answer.draft = ComposerDraft::default(); + answer.answer_committed = false; + answer.notes_visible = false; + } + self.pending_submission_draft = None; + self.composer + .set_text_content(String::new(), Vec::new(), Vec::new()); + self.composer.move_cursor_to_end(); + self.focus = Focus::Options; + self.sync_composer_placeholder(); + } + /// Ensure there is a selection before allowing notes entry. fn ensure_selected_for_notes(&mut self) { if let Some(answer) = self.current_answer_mut() { @@ -976,6 +995,10 @@ impl BottomPaneView for RequestUserInputOverlay { } if matches!(key_event.code, KeyCode::Esc) { + if self.has_options() && self.notes_ui_visible() { + self.clear_notes_and_focus_options(); + return; + } // TODO: Emit interrupted request_user_input results (including committed answers) // once core supports persisting them reliably without follow-up turn issues. self.app_event_tx.send(AppEvent::CodexOp(Op::Interrupt)); @@ -1093,16 +1116,7 @@ impl BottomPaneView for RequestUserInputOverlay { Focus::Notes => { let notes_empty = self.composer.current_text_with_pending().trim().is_empty(); if self.has_options() && matches!(key_event.code, KeyCode::Tab) { - if let Some(answer) = self.current_answer_mut() { - answer.draft = ComposerDraft::default(); - answer.answer_committed = false; - answer.notes_visible = false; - } - self.composer - .set_text_content(String::new(), Vec::new(), Vec::new()); - self.composer.move_cursor_to_end(); - self.focus = Focus::Options; - self.sync_composer_placeholder(); + self.clear_notes_and_focus_options(); return; } if self.has_options() && matches!(key_event.code, KeyCode::Backspace) && notes_empty @@ -1753,7 +1767,7 @@ mod tests { } #[test] - fn esc_in_notes_mode_interrupts() { + fn esc_in_notes_mode_clears_notes_and_hides_ui() { let (tx, mut rx) = test_sender(); let mut overlay = RequestUserInputOverlay::new( request_event("turn-1", vec![question_with_options("q1", "Pick one")]), @@ -1769,12 +1783,19 @@ mod tests { overlay.handle_key_event(KeyEvent::from(KeyCode::Tab)); overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); - assert_eq!(overlay.done, true); - expect_interrupt_only(&mut rx); + let answer = overlay.current_answer().expect("answer missing"); + assert_eq!(overlay.done, false); + assert!(matches!(overlay.focus, Focus::Options)); + assert_eq!(overlay.notes_ui_visible(), false); + assert_eq!(overlay.composer.current_text_with_pending(), ""); + assert_eq!(answer.draft.text, ""); + assert_eq!(answer.options_state.selected_idx, Some(0)); + assert_eq!(answer.answer_committed, false); + assert!(rx.try_recv().is_err()); } #[test] - fn esc_in_notes_mode_interrupts_with_notes_visible() { + fn esc_in_notes_mode_with_text_clears_notes_and_hides_ui() { let (tx, mut rx) = test_sender(); let mut overlay = RequestUserInputOverlay::new( request_event("turn-1", vec![question_with_options("q1", "Pick one")]), @@ -1791,8 +1812,15 @@ mod tests { overlay.handle_key_event(KeyEvent::from(KeyCode::Char('a'))); overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); - assert_eq!(overlay.done, true); - expect_interrupt_only(&mut rx); + let answer = overlay.current_answer().expect("answer missing"); + assert_eq!(overlay.done, false); + assert!(matches!(overlay.focus, Focus::Options)); + assert_eq!(overlay.notes_ui_visible(), false); + assert_eq!(overlay.composer.current_text_with_pending(), ""); + assert_eq!(answer.draft.text, ""); + assert_eq!(answer.options_state.selected_idx, Some(0)); + assert_eq!(answer.answer_committed, false); + assert!(rx.try_recv().is_err()); } #[test] diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap index d9d219b627..a4540a2b26 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2321 expression: "render_snapshot(&overlay, area)" --- @@ -16,4 +17,4 @@ expression: "render_snapshot(&overlay, area)" - tab to clear notes | enter to submit answer | esc to interrupt + tab or esc to clear notes | enter to submit answer diff --git a/docs/tui-request-user-input.md b/docs/tui-request-user-input.md index f8eb7aeb73..8ca6f5369b 100644 --- a/docs/tui-request-user-input.md +++ b/docs/tui-request-user-input.md @@ -30,7 +30,9 @@ friction for freeform input. - Enter advances to the next question. - Enter on the last question submits all answers. - PageUp/PageDown navigate across questions (when multiple are present). -- Esc interrupts the run. +- Esc interrupts the run in option selection mode. +- When notes are open for an option question, Tab or Esc clears notes and returns + to option selection. ## Layout priorities From fcaed4cb88f068ffaa3cb9cfc69e7cf6605de0ce Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Tue, 3 Feb 2026 18:04:07 -0800 Subject: [PATCH 129/139] feat: log webscocket timing into runtime metrics (#10577) --- codex-rs/core/src/client.rs | 8 ++ codex-rs/core/src/lib.rs | 1 + .../core/tests/suite/client_websockets.rs | 77 +++++++++++++++++++ codex-rs/otel/src/metrics/names.rs | 4 + codex-rs/otel/src/metrics/runtime_metrics.rs | 12 +++ codex-rs/otel/src/traces/otel_manager.rs | 38 +++++++++ codex-rs/otel/tests/suite/runtime_summary.rs | 14 +++- codex-rs/tui/src/history_cell.rs | 14 +++- 8 files changed, 165 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index fcc308f380..314bfe7b40 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -72,6 +72,8 @@ use crate::transport_manager::TransportManager; pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; +pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = + "x-responsesapi-include-timing-metrics"; #[derive(Debug, Default)] struct TurnMetadataCache { @@ -489,6 +491,12 @@ impl ModelClientSession { if needs_new { let mut headers = options.extra_headers.clone(); headers.extend(build_conversation_headers(options.conversation_id.clone())); + if self.state.config.features.enabled(Feature::RuntimeMetrics) { + headers.insert( + X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER, + HeaderValue::from_static("true"), + ); + } let websocket_telemetry = self.build_websocket_telemetry(); let new_conn: ApiWebSocketConnection = ApiWebSocketResponsesClient::new(api_provider, api_auth) diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index c9d67e19f1..c5ac0d8f85 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -61,6 +61,7 @@ pub mod token_data; mod truncate; mod unified_exec; pub mod windows_sandbox; +pub use client::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER; pub use model_provider_info::DEFAULT_LMSTUDIO_PORT; pub use model_provider_info::DEFAULT_OLLAMA_PORT; pub use model_provider_info::LMSTUDIO_OSS_PROVIDER_ID; diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 51c94edd7b..9f66283442 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -10,6 +10,7 @@ use codex_core::ResponseEvent; use codex_core::ResponseItem; use codex_core::TransportManager; use codex_core::WireApi; +use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER; use codex_core::features::Feature; use codex_core::models_manager::manager::ModelsManager; use codex_core::protocol::SessionSource; @@ -102,6 +103,72 @@ async fn responses_websocket_emits_websocket_telemetry_events() { server.shutdown().await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_websocket_includes_timing_metrics_header_when_runtime_metrics_enabled() { + skip_if_no_network!(); + + let server = start_websocket_server(vec![vec![vec![ + ev_response_created("resp-1"), + serde_json::json!({ + "type": "responsesapi.websocket_timing", + "timing_metrics": { + "responses_duration_excl_engine_and_client_tool_time_ms": 120, + "engine_service_total_ms": 450 + } + }), + ev_completed("resp-1"), + ]]]) + .await; + + let harness = websocket_harness_with_runtime_metrics(&server, true).await; + harness.otel_manager.reset_runtime_metrics(); + let mut session = harness.client.new_session(None); + let prompt = prompt_with_input(vec![message_item("hello")]); + + stream_until_complete(&mut session, &prompt).await; + tokio::time::sleep(Duration::from_millis(10)).await; + + let handshake = server.single_handshake(); + assert_eq!( + handshake.header(X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER), + Some("true".to_string()) + ); + + let summary = harness + .otel_manager + .runtime_metrics_summary() + .expect("runtime metrics summary"); + assert_eq!(summary.responses_api_overhead_ms, 120); + assert_eq!(summary.responses_api_inference_time_ms, 450); + + server.shutdown().await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_websocket_omits_timing_metrics_header_when_runtime_metrics_disabled() { + skip_if_no_network!(); + + let server = start_websocket_server(vec![vec![vec![ + ev_response_created("resp-1"), + ev_completed("resp-1"), + ]]]) + .await; + + let harness = websocket_harness_with_runtime_metrics(&server, false).await; + let mut session = harness.client.new_session(None); + let prompt = prompt_with_input(vec![message_item("hello")]); + + stream_until_complete(&mut session, &prompt).await; + + let handshake = server.single_handshake(); + assert_eq!( + handshake.header(X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER), + None + ); + + server.shutdown().await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn responses_websocket_emits_reasoning_included_event() { skip_if_no_network!(); @@ -241,11 +308,21 @@ fn websocket_provider(server: &WebSocketTestServer) -> ModelProviderInfo { } async fn websocket_harness(server: &WebSocketTestServer) -> WebsocketTestHarness { + websocket_harness_with_runtime_metrics(server, false).await +} + +async fn websocket_harness_with_runtime_metrics( + server: &WebSocketTestServer, + runtime_metrics_enabled: bool, +) -> WebsocketTestHarness { let provider = websocket_provider(server); let codex_home = TempDir::new().unwrap(); let mut config = load_default_config_for_test(&codex_home).await; config.model = Some(MODEL.to_string()); config.features.enable(Feature::ResponsesWebsockets); + if runtime_metrics_enabled { + config.features.enable(Feature::RuntimeMetrics); + } let config = Arc::new(config); let model_info = ModelsManager::construct_model_info_offline(MODEL, &config); let conversation_id = ThreadId::new(); diff --git a/codex-rs/otel/src/metrics/names.rs b/codex-rs/otel/src/metrics/names.rs index b8ff9364ad..97c4f4e6ed 100644 --- a/codex-rs/otel/src/metrics/names.rs +++ b/codex-rs/otel/src/metrics/names.rs @@ -8,3 +8,7 @@ pub(crate) const WEBSOCKET_REQUEST_COUNT_METRIC: &str = "codex.websocket.request pub(crate) const WEBSOCKET_REQUEST_DURATION_METRIC: &str = "codex.websocket.request.duration_ms"; pub(crate) const WEBSOCKET_EVENT_COUNT_METRIC: &str = "codex.websocket.event"; pub(crate) const WEBSOCKET_EVENT_DURATION_METRIC: &str = "codex.websocket.event.duration_ms"; +pub(crate) const RESPONSES_API_OVERHEAD_DURATION_METRIC: &str = + "codex.responses_api_overhead.duration_ms"; +pub(crate) const RESPONSES_API_INFERENCE_TIME_DURATION_METRIC: &str = + "codex.responses_api_inference_time.duration_ms"; diff --git a/codex-rs/otel/src/metrics/runtime_metrics.rs b/codex-rs/otel/src/metrics/runtime_metrics.rs index dbd28010f6..d59ff4b1b7 100644 --- a/codex-rs/otel/src/metrics/runtime_metrics.rs +++ b/codex-rs/otel/src/metrics/runtime_metrics.rs @@ -1,5 +1,7 @@ use crate::metrics::names::API_CALL_COUNT_METRIC; use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_INFERENCE_TIME_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_OVERHEAD_DURATION_METRIC; use crate::metrics::names::SSE_EVENT_COUNT_METRIC; use crate::metrics::names::SSE_EVENT_DURATION_METRIC; use crate::metrics::names::TOOL_CALL_COUNT_METRIC; @@ -32,6 +34,8 @@ pub struct RuntimeMetricsSummary { pub streaming_events: RuntimeMetricTotals, pub websocket_calls: RuntimeMetricTotals, pub websocket_events: RuntimeMetricTotals, + pub responses_api_overhead_ms: u64, + pub responses_api_inference_time_ms: u64, } impl RuntimeMetricsSummary { @@ -41,6 +45,8 @@ impl RuntimeMetricsSummary { && self.streaming_events.is_empty() && self.websocket_calls.is_empty() && self.websocket_events.is_empty() + && self.responses_api_overhead_ms == 0 + && self.responses_api_inference_time_ms == 0 } pub(crate) fn from_snapshot(snapshot: &ResourceMetrics) -> Self { @@ -64,12 +70,18 @@ impl RuntimeMetricsSummary { count: sum_counter(snapshot, WEBSOCKET_EVENT_COUNT_METRIC), duration_ms: sum_histogram_ms(snapshot, WEBSOCKET_EVENT_DURATION_METRIC), }; + let responses_api_overhead_ms = + sum_histogram_ms(snapshot, RESPONSES_API_OVERHEAD_DURATION_METRIC); + let responses_api_inference_time_ms = + sum_histogram_ms(snapshot, RESPONSES_API_INFERENCE_TIME_DURATION_METRIC); Self { tool_calls, api_calls, streaming_events, websocket_calls, websocket_events, + responses_api_overhead_ms, + responses_api_inference_time_ms, } } } diff --git a/codex-rs/otel/src/traces/otel_manager.rs b/codex-rs/otel/src/traces/otel_manager.rs index c585ec67d5..bde9515c20 100644 --- a/codex-rs/otel/src/traces/otel_manager.rs +++ b/codex-rs/otel/src/traces/otel_manager.rs @@ -1,5 +1,7 @@ use crate::metrics::names::API_CALL_COUNT_METRIC; use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_INFERENCE_TIME_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_OVERHEAD_DURATION_METRIC; use crate::metrics::names::SSE_EVENT_COUNT_METRIC; use crate::metrics::names::SSE_EVENT_DURATION_METRIC; use crate::metrics::names::TOOL_CALL_COUNT_METRIC; @@ -42,6 +44,10 @@ pub use crate::ToolDecisionSource; const SSE_UNKNOWN_KIND: &str = "unknown"; const WEBSOCKET_UNKNOWN_KIND: &str = "unknown"; +const RESPONSES_WEBSOCKET_TIMING_KIND: &str = "responsesapi.websocket_timing"; +const RESPONSES_WEBSOCKET_TIMING_METRICS_FIELD: &str = "timing_metrics"; +const RESPONSES_API_OVERHEAD_FIELD: &str = "responses_duration_excl_engine_and_client_tool_time_ms"; +const RESPONSES_API_INFERENCE_FIELD: &str = "engine_service_total_ms"; impl OtelManager { #[allow(clippy::too_many_arguments)] @@ -252,6 +258,9 @@ impl OtelManager { .get("type") .and_then(|value| value.as_str()) .map(std::string::ToString::to_string); + if kind.as_deref() == Some(RESPONSES_WEBSOCKET_TIMING_KIND) { + self.record_responses_websocket_timing_metrics(&value); + } if kind.as_deref() == Some("response.failed") { success = false; error_message = value @@ -651,6 +660,22 @@ impl OtelManager { ); } + fn record_responses_websocket_timing_metrics(&self, value: &serde_json::Value) { + let timing_metrics = value.get(RESPONSES_WEBSOCKET_TIMING_METRICS_FIELD); + + let overhead_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_OVERHEAD_FIELD)); + if let Some(duration) = duration_from_ms_value(overhead_value) { + self.record_duration(RESPONSES_API_OVERHEAD_DURATION_METRIC, duration, &[]); + } + + let inference_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_INFERENCE_FIELD)); + if let Some(duration) = duration_from_ms_value(inference_value) { + self.record_duration(RESPONSES_API_INFERENCE_TIME_DURATION_METRIC, duration, &[]); + } + } + fn responses_type(event: &ResponseEvent) -> String { match event { ResponseEvent::Created => "created".into(), @@ -689,3 +714,16 @@ impl OtelManager { fn timestamp() -> String { Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true) } + +fn duration_from_ms_value(value: Option<&serde_json::Value>) -> Option { + let value = value?; + let ms = value + .as_f64() + .or_else(|| value.as_i64().map(|v| v as f64)) + .or_else(|| value.as_u64().map(|v| v as f64))?; + if !ms.is_finite() || ms < 0.0 { + return None; + } + let clamped = ms.min(u64::MAX as f64); + Some(Duration::from_millis(clamped.round() as u64)) +} diff --git a/codex-rs/otel/tests/suite/runtime_summary.rs b/codex-rs/otel/tests/suite/runtime_summary.rs index 78aed8a0a7..811c801fce 100644 --- a/codex-rs/otel/tests/suite/runtime_summary.rs +++ b/codex-rs/otel/tests/suite/runtime_summary.rs @@ -62,6 +62,14 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( r#"{"type":"response.created"}"#.into(), )))); manager.record_websocket_event(&ws_response, Duration::from_millis(80)); + let ws_timing_response: std::result::Result< + Option>, + codex_api::ApiError, + > = Ok(Some(Ok(Message::Text( + r#"{"type":"responsesapi.websocket_timing","timing_metrics":{"responses_duration_excl_engine_and_client_tool_time_ms":124,"engine_service_total_ms":457}}"# + .into(), + )))); + manager.record_websocket_event(&ws_timing_response, Duration::from_millis(20)); let summary = manager .runtime_metrics_summary() @@ -84,9 +92,11 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( duration_ms: 400, }, websocket_events: RuntimeMetricTotals { - count: 1, - duration_ms: 80, + count: 2, + duration_ms: 100, }, + responses_api_overhead_ms: 124, + responses_api_inference_time_ms: 457, }; assert_eq!(summary, expected); diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 49ccc481bf..b85ca8f6bb 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -2213,6 +2213,14 @@ fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { summary.websocket_events.count )); } + if summary.responses_api_overhead_ms > 0 { + let duration = format_duration_ms(summary.responses_api_overhead_ms); + parts.push(format!("Responses API overhead: {duration}")); + } + if summary.responses_api_inference_time_ms > 0 { + let duration = format_duration_ms(summary.responses_api_inference_time_ms); + parts.push(format!("Responses API inference: {duration}")); + } if parts.is_empty() { None } else { @@ -2381,9 +2389,11 @@ mod tests { count: 4, duration_ms: 1_200, }, + responses_api_overhead_ms: 650, + responses_api_inference_time_ms: 1_940, }; let cell = FinalMessageSeparator::new(Some(12), Some(summary)); - let rendered = render_lines(&cell.display_lines(200)); + let rendered = render_lines(&cell.display_lines(300)); assert_eq!(rendered.len(), 1); assert!(!rendered[0].contains("Worked for")); @@ -2392,6 +2402,8 @@ mod tests { assert!(rendered[0].contains("WebSocket: 1 events send (700ms)")); assert!(rendered[0].contains("Streams: 6 events (900ms)")); assert!(rendered[0].contains("4 events received (1.2s)")); + assert!(rendered[0].contains("Responses API overhead: 650ms")); + assert!(rendered[0].contains("Responses API inference: 1.9s")); } #[test] From 38a47700b526a7ae3954d2dd8a233d166bc0a7b8 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 3 Feb 2026 18:15:55 -0800 Subject: [PATCH 130/139] Add thread/compact v2 (#10445) - add `thread/compact` as a trigger-only v2 RPC that submits `Op::Compact` and returns `{}` immediately. - add v2 compaction e2e coverage for success and invalid/unknown thread ids, and update protocol schemas/docs. --- .../schema/json/ClientRequest.json | 35 +++++ .../codex_app_server_protocol.schemas.json | 42 ++++++ .../json/v2/ThreadCompactStartParams.json | 13 ++ .../json/v2/ThreadCompactStartResponse.json | 5 + .../schema/typescript/ClientRequest.ts | 3 +- .../typescript/v2/ThreadCompactStartParams.ts | 5 + .../v2/ThreadCompactStartResponse.ts | 5 + .../schema/typescript/v2/index.ts | 2 + .../src/protocol/common.rs | 4 + .../app-server-protocol/src/protocol/v2.rs | 12 ++ codex-rs/app-server/README.md | 17 +++ .../app-server/src/codex_message_processor.rs | 29 ++++ .../app-server/tests/common/mcp_process.rs | 10 ++ .../app-server/tests/suite/v2/compaction.rs | 132 ++++++++++++++++++ 14 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 39faeb1378..9b676f8e52 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2235,6 +2235,17 @@ ], "type": "object" }, + "ThreadCompactStartParams": { + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, "ThreadForkParams": { "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", "properties": { @@ -3210,6 +3221,30 @@ "title": "Thread/unarchiveRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/compact/start" + ], + "title": "Thread/compact/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadCompactStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/compact/startRequest", + "type": "object" + }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index b813b5946c..f0b16e2358 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -598,6 +598,30 @@ "title": "Thread/unarchiveRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/compact/start" + ], + "title": "Thread/compact/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadCompactStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/compact/startRequest", + "type": "object" + }, { "properties": { "id": { @@ -13855,6 +13879,24 @@ "title": "ThreadArchiveResponse", "type": "object" }, + "ThreadCompactStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadCompactStartParams", + "type": "object" + }, + "ThreadCompactStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadCompactStartResponse", + "type": "object" + }, "ThreadForkParams": { "$schema": "http://json-schema.org/draft-07/schema#", "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json new file mode 100644 index 0000000000..a174ff95d9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadCompactStartParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json new file mode 100644 index 0000000000..bb372b6ddd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadCompactStartResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index d526e5c5e9..176f86be56 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -39,6 +39,7 @@ import type { SkillsListParams } from "./v2/SkillsListParams"; import type { SkillsRemoteReadParams } from "./v2/SkillsRemoteReadParams"; import type { SkillsRemoteWriteParams } from "./v2/SkillsRemoteWriteParams"; import type { ThreadArchiveParams } from "./v2/ThreadArchiveParams"; +import type { ThreadCompactStartParams } from "./v2/ThreadCompactStartParams"; import type { ThreadForkParams } from "./v2/ThreadForkParams"; import type { ThreadListParams } from "./v2/ThreadListParams"; import type { ThreadLoadedListParams } from "./v2/ThreadLoadedListParams"; @@ -54,4 +55,4 @@ import type { TurnStartParams } from "./v2/TurnStartParams"; /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.ts new file mode 100644 index 0000000000..a60b2c2812 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadCompactStartParams = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.ts new file mode 100644 index 0000000000..3794feb270 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadCompactStartResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 83357ff5c6..ed4f74d4ad 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -128,6 +128,8 @@ export type { TextRange } from "./TextRange"; export type { Thread } from "./Thread"; export type { ThreadArchiveParams } from "./ThreadArchiveParams"; export type { ThreadArchiveResponse } from "./ThreadArchiveResponse"; +export type { ThreadCompactStartParams } from "./ThreadCompactStartParams"; +export type { ThreadCompactStartResponse } from "./ThreadCompactStartResponse"; export type { ThreadForkParams } from "./ThreadForkParams"; export type { ThreadForkResponse } from "./ThreadForkResponse"; export type { ThreadItem } from "./ThreadItem"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index bb2510684e..c84d28ae89 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -208,6 +208,10 @@ client_request_definitions! { params: v2::ThreadUnarchiveParams, response: v2::ThreadUnarchiveResponse, }, + ThreadCompactStart => "thread/compact/start" { + params: v2::ThreadCompactStartParams, + response: v2::ThreadCompactStartResponse, + }, ThreadRollback => "thread/rollback" { params: v2::ThreadRollbackParams, response: v2::ThreadRollbackResponse, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index dbc51fa9f9..8e7beccf79 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1408,6 +1408,18 @@ pub struct ThreadUnarchiveResponse { pub thread: Thread, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadCompactStartParams { + pub thread_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadCompactStartResponse {} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 78a4f3cf94..055ca6f32a 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -85,6 +85,7 @@ Example (from OpenAI's official VSCode extension): - `thread/archive` — move a thread’s rollout file into the archived directory; returns `{}` on success. - `thread/name/set` — set or update a thread’s user-facing name; returns `{}` on success. Thread names are not required to be unique; name lookups resolve to the most recently updated thread. - `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success. +- `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications. - `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success. - `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. - `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`. @@ -239,6 +240,22 @@ Use `thread/unarchive` to move an archived rollout back into the sessions direct { "id": 24, "result": { "thread": { "id": "thr_b" } } } ``` +### Example: Trigger thread compaction + +Use `thread/compact/start` to trigger manual history compaction for a thread. The request returns immediately with `{}`. + +Progress is emitted as standard `turn/*` and `item/*` notifications on the same `threadId`. Clients should expect a single compaction item: + +- `item/started` with `item: { "type": "contextCompaction", ... }` +- `item/completed` with the same `contextCompaction` item id + +While compaction is running, the thread is effectively in a turn so clients should surface progress UI based on the notifications. + +```json +{ "method": "thread/compact/start", "id": 25, "params": { "threadId": "thr_b" } } +{ "id": 25, "result": {} } +``` + ### Example: Start a turn (send user input) Turns attach user input (text or images) to a thread and trigger Codex generation. The `input` field is a list of discriminated unions: diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 866998a4fd..219cb46570 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -104,6 +104,8 @@ use codex_app_server_protocol::SkillsRemoteWriteResponse; use codex_app_server_protocol::Thread; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; +use codex_app_server_protocol::ThreadCompactStartParams; +use codex_app_server_protocol::ThreadCompactStartResponse; use codex_app_server_protocol::ThreadForkParams; use codex_app_server_protocol::ThreadForkResponse; use codex_app_server_protocol::ThreadItem; @@ -455,6 +457,9 @@ impl CodexMessageProcessor { ClientRequest::ThreadUnarchive { request_id, params } => { self.thread_unarchive(request_id, params).await; } + ClientRequest::ThreadCompactStart { request_id, params } => { + self.thread_compact_start(request_id, params).await; + } ClientRequest::ThreadRollback { request_id, params } => { self.thread_rollback(request_id, params).await; } @@ -2094,6 +2099,30 @@ impl CodexMessageProcessor { } } + async fn thread_compact_start(&self, request_id: RequestId, params: ThreadCompactStartParams) { + let ThreadCompactStartParams { thread_id } = params; + + let (_, thread) = match self.load_thread(&thread_id).await { + Ok(v) => v, + Err(error) => { + self.outgoing.send_error(request_id, error).await; + return; + } + }; + + match thread.submit(Op::Compact).await { + Ok(_) => { + self.outgoing + .send_response(request_id, ThreadCompactStartResponse {}) + .await; + } + Err(err) => { + self.send_internal_error(request_id, format!("failed to start compaction: {err}")) + .await; + } + } + } + async fn thread_list(&self, request_id: RequestId, params: ThreadListParams) { let ThreadListParams { cursor, diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 4804a8cd37..ba6dc058e6 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -50,6 +50,7 @@ use codex_app_server_protocol::SendUserTurnParams; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::SetDefaultModelParams; use codex_app_server_protocol::ThreadArchiveParams; +use codex_app_server_protocol::ThreadCompactStartParams; use codex_app_server_protocol::ThreadForkParams; use codex_app_server_protocol::ThreadListParams; use codex_app_server_protocol::ThreadLoadedListParams; @@ -418,6 +419,15 @@ impl McpProcess { self.send_request("thread/unarchive", params).await } + /// Send a `thread/compact/start` JSON-RPC request. + pub async fn send_thread_compact_start_request( + &mut self, + params: ThreadCompactStartParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("thread/compact/start", params).await + } + /// Send a `thread/rollback` JSON-RPC request. pub async fn send_thread_rollback_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/v2/compaction.rs b/codex-rs/app-server/tests/suite/v2/compaction.rs index eeedcb286b..4730d920c8 100644 --- a/codex-rs/app-server/tests/suite/v2/compaction.rs +++ b/codex-rs/app-server/tests/suite/v2/compaction.rs @@ -15,9 +15,12 @@ use app_test_support::write_chatgpt_auth; use app_test_support::write_mock_responses_config_toml; use codex_app_server_protocol::ItemCompletedNotification; use codex_app_server_protocol::ItemStartedNotification; +use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCNotification; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ThreadCompactStartParams; +use codex_app_server_protocol::ThreadCompactStartResponse; use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; @@ -39,6 +42,7 @@ use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); const AUTO_COMPACT_LIMIT: i64 = 1_000; const COMPACT_PROMPT: &str = "Summarize the conversation."; +const INVALID_REQUEST_ERROR_CODE: i64 = -32600; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()> { @@ -196,6 +200,134 @@ async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<() Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn thread_compact_start_triggers_compaction_and_returns_empty_response() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let sse = responses::sse(vec![ + responses::ev_assistant_message("m1", "MANUAL_COMPACT_SUMMARY"), + responses::ev_completed_with_tokens("r1", 200), + ]); + responses::mount_sse_sequence(&server, vec![sse]).await; + + let codex_home = TempDir::new()?; + write_mock_responses_config_toml( + codex_home.path(), + &server.uri(), + &BTreeMap::default(), + AUTO_COMPACT_LIMIT, + None, + "mock_provider", + COMPACT_PROMPT, + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_id = start_thread(&mut mcp).await?; + let compact_id = mcp + .send_thread_compact_start_request(ThreadCompactStartParams { + thread_id: thread_id.clone(), + }) + .await?; + let compact_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(compact_id)), + ) + .await??; + let _compact: ThreadCompactStartResponse = + to_response::(compact_resp)?; + + let started = wait_for_context_compaction_started(&mut mcp).await?; + let completed = wait_for_context_compaction_completed(&mut mcp).await?; + + let ThreadItem::ContextCompaction { id: started_id } = started.item else { + unreachable!("started item should be context compaction"); + }; + let ThreadItem::ContextCompaction { id: completed_id } = completed.item else { + unreachable!("completed item should be context compaction"); + }; + + assert_eq!(started.thread_id, thread_id); + assert_eq!(completed.thread_id, thread_id); + assert_eq!(started_id, completed_id); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn thread_compact_start_rejects_invalid_thread_id() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let codex_home = TempDir::new()?; + write_mock_responses_config_toml( + codex_home.path(), + &server.uri(), + &BTreeMap::default(), + AUTO_COMPACT_LIMIT, + None, + "mock_provider", + COMPACT_PROMPT, + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_thread_compact_start_request(ThreadCompactStartParams { + thread_id: "not-a-thread-id".to_string(), + }) + .await?; + let error: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(request_id)), + ) + .await??; + + assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE); + assert!(error.error.message.contains("invalid thread id")); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn thread_compact_start_rejects_unknown_thread_id() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let codex_home = TempDir::new()?; + write_mock_responses_config_toml( + codex_home.path(), + &server.uri(), + &BTreeMap::default(), + AUTO_COMPACT_LIMIT, + None, + "mock_provider", + COMPACT_PROMPT, + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_thread_compact_start_request(ThreadCompactStartParams { + thread_id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), + }) + .await?; + let error: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(request_id)), + ) + .await??; + + assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE); + assert!(error.error.message.contains("thread not found")); + + Ok(()) +} + async fn start_thread(mcp: &mut McpProcess) -> Result { let thread_id = mcp .send_thread_start_request(ThreadStartParams { From 56ebfff1a800095fb501c80e9035455a23b4e43d Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 3 Feb 2026 21:59:13 -0800 Subject: [PATCH 131/139] Move metadata calculation out of client (#10589) Model client shouldn't be responsible for this. --- codex-rs/core/src/client.rs | 61 ++----------- codex-rs/core/src/codex.rs | 51 +++++++++-- codex-rs/core/src/compact.rs | 5 +- codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/turn_metadata.rs | 2 +- codex-rs/core/tests/responses_headers.rs | 105 +++-------------------- 6 files changed, 70 insertions(+), 155 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 314bfe7b40..1d0dc7fb65 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -1,13 +1,10 @@ -use std::path::PathBuf; use std::sync::Arc; use std::sync::OnceLock; -use std::sync::RwLock; use crate::api_bridge::CoreAuthProvider; use crate::api_bridge::auth_provider_from_auth; use crate::api_bridge::map_api_error; use crate::auth::UnauthorizedRecovery; -use crate::turn_metadata::build_turn_metadata_header; use codex_api::CompactClient as ApiCompactClient; use codex_api::CompactionInput as ApiCompactionInput; use codex_api::Prompt as ApiPrompt; @@ -75,12 +72,6 @@ pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = "x-responsesapi-include-timing-metrics"; -#[derive(Debug, Default)] -struct TurnMetadataCache { - cwd: Option, - header: Option, -} - #[derive(Debug)] struct ModelClientState { config: Arc, @@ -93,7 +84,6 @@ struct ModelClientState { summary: ReasoningSummaryConfig, session_source: SessionSource, transport_manager: TransportManager, - turn_metadata_cache: Arc>, } #[derive(Debug, Clone)] @@ -106,6 +96,7 @@ pub struct ModelClientSession { connection: Option, websocket_last_items: Vec, transport_manager: TransportManager, + turn_metadata_header: Option, /// Turn state for sticky routing. /// /// This is an `OnceLock` that stores the turn state value received from the server @@ -145,53 +136,20 @@ impl ModelClient { summary, session_source, transport_manager, - turn_metadata_cache: Arc::new(RwLock::new(TurnMetadataCache::default())), }), } } - pub fn new_session(&self, turn_metadata_cwd: Option) -> ModelClientSession { - self.prewarm_turn_metadata_header(turn_metadata_cwd); + pub fn new_session(&self, turn_metadata_header: Option) -> ModelClientSession { ModelClientSession { state: Arc::clone(&self.state), connection: None, websocket_last_items: Vec::new(), transport_manager: self.state.transport_manager.clone(), + turn_metadata_header, turn_state: Arc::new(OnceLock::new()), } } - - /// Refresh turn metadata in the background and update a cached header that request - /// builders can read without blocking. - fn prewarm_turn_metadata_header(&self, turn_metadata_cwd: Option) { - let turn_metadata_cwd = - turn_metadata_cwd.map(|cwd| std::fs::canonicalize(&cwd).unwrap_or(cwd)); - - if let Ok(mut cache) = self.state.turn_metadata_cache.write() - && cache.cwd != turn_metadata_cwd - { - cache.cwd = turn_metadata_cwd.clone(); - cache.header = None; - } - - let Some(cwd) = turn_metadata_cwd else { - return; - }; - let turn_metadata_cache = Arc::clone(&self.state.turn_metadata_cache); - if let Ok(handle) = tokio::runtime::Handle::try_current() { - let _task = handle.spawn(async move { - let header = build_turn_metadata_header(cwd.as_path()) - .await - .and_then(|value| HeaderValue::from_str(value.as_str()).ok()); - - if let Ok(mut cache) = turn_metadata_cache.write() - && cache.cwd.as_ref() == Some(&cwd) - { - cache.header = header; - } - }); - } - } } impl ModelClient { @@ -300,14 +258,6 @@ impl ModelClient { } impl ModelClientSession { - fn turn_metadata_header(&self) -> Option { - self.state - .turn_metadata_cache - .try_read() - .ok() - .and_then(|cache| cache.header.clone()) - } - /// Streams a single model turn using the configured Responses transport. pub async fn stream(&mut self, prompt: &Prompt) -> Result { let wire_api = self.state.provider.wire_api; @@ -364,7 +314,10 @@ impl ModelClientSession { prompt: &Prompt, compression: Compression, ) -> ApiResponsesOptions { - let turn_metadata_header = self.turn_metadata_header(); + let turn_metadata_header = self + .turn_metadata_header + .as_deref() + .and_then(|value| HeaderValue::from_str(value).ok()); let model_info = &self.state.model_info; let default_reasoning_effort = model_info.default_reasoning_level; diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index b7c0404056..7fe3b8c810 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -34,6 +34,7 @@ use crate::stream_events_utils::last_assistant_message_from_item; use crate::terminal; use crate::transport_manager::TransportManager; use crate::truncate::TruncationPolicy; +use crate::turn_metadata::build_turn_metadata_header; use crate::user_notification::UserNotifier; use crate::util::error_or_panic; use async_channel::Receiver; @@ -80,6 +81,7 @@ use rmcp::model::RequestId; use serde_json; use serde_json::Value; use tokio::sync::Mutex; +use tokio::sync::OnceCell; use tokio::sync::RwLock; use tokio::sync::oneshot; use tokio_util::sync::CancellationToken; @@ -90,6 +92,7 @@ use tracing::field; use tracing::info; use tracing::info_span; use tracing::instrument; +use tracing::trace; use tracing::trace_span; use tracing::warn; @@ -501,6 +504,7 @@ pub(crate) struct TurnContext { pub(crate) tool_call_gate: Arc, pub(crate) truncation_policy: TruncationPolicy, pub(crate) dynamic_tools: Vec, + turn_metadata_header: OnceCell>, } impl TurnContext { pub(crate) fn resolve_path(&self, path: Option) -> PathBuf { @@ -514,6 +518,38 @@ impl TurnContext { .as_deref() .unwrap_or(compact::SUMMARIZATION_PROMPT) } + + async fn build_turn_metadata_header(&self) -> Option { + self.turn_metadata_header + .get_or_init(|| async { build_turn_metadata_header(self.cwd.as_path()).await }) + .await + .clone() + } + + pub async fn resolve_turn_metadata_header(&self) -> Option { + const TURN_METADATA_HEADER_TIMEOUT_MS: u64 = 250; + match tokio::time::timeout( + std::time::Duration::from_millis(TURN_METADATA_HEADER_TIMEOUT_MS), + self.build_turn_metadata_header(), + ) + .await + { + Ok(header) => header, + Err(_) => { + warn!("timed out after 250ms while building turn metadata header"); + self.turn_metadata_header.get().cloned().flatten() + } + } + } + + pub fn spawn_turn_metadata_header_task(self: &Arc) { + let context = Arc::clone(self); + tokio::spawn(async move { + trace!("Spawning turn metadata calculation task"); + context.build_turn_metadata_header().await; + trace!("Turn metadata calculation task completed"); + }); + } } #[derive(Clone)] @@ -682,10 +718,11 @@ impl Session { web_search_mode: per_turn_config.web_search_mode, }); + let cwd = session_configuration.cwd.clone(); TurnContext { sub_id, client, - cwd: session_configuration.cwd.clone(), + cwd, developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), user_instructions: session_configuration.user_instructions.clone(), @@ -702,6 +739,7 @@ impl Session { tool_call_gate: Arc::new(ReadinessFlag::new()), truncation_policy: model_info.truncation_policy.into(), dynamic_tools: session_configuration.dynamic_tools.clone(), + turn_metadata_header: OnceCell::new(), } } @@ -1246,10 +1284,13 @@ impl Session { sub_id, self.services.transport_manager.clone(), ); + if let Some(final_schema) = final_output_json_schema { turn_context.final_output_json_schema = final_schema; } - Arc::new(turn_context) + let turn_context = Arc::new(turn_context); + turn_context.spawn_turn_metadata_header_task(); + turn_context } pub(crate) async fn new_default_turn(&self) -> Arc { @@ -3274,6 +3315,7 @@ async fn spawn_review_thread( tool_call_gate: Arc::new(ReadinessFlag::new()), dynamic_tools: parent_turn_context.dynamic_tools.clone(), truncation_policy: model_info.truncation_policy.into(), + turn_metadata_header: parent_turn_context.turn_metadata_header.clone(), }; // Seed the child task with the review prompt as the initial user message. @@ -3478,9 +3520,8 @@ pub(crate) async fn run_turn( // many turns, from the perspective of the user, it is a single turn. let turn_diff_tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); - let mut client_session = turn_context - .client - .new_session(Some(turn_context.cwd.clone())); + let turn_metadata_header = turn_context.resolve_turn_metadata_header().await; + let mut client_session = turn_context.client.new_session(turn_metadata_header); loop { // Note that pending_input would be something like a message the user diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index ee94e4994b..7ae773884c 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -337,9 +337,8 @@ async fn drain_to_completed( turn_context: &TurnContext, prompt: &Prompt, ) -> CodexResult<()> { - let mut client_session = turn_context - .client - .new_session(Some(turn_context.cwd.clone())); + let turn_metadata_header = turn_context.resolve_turn_metadata_header().await; + let mut client_session = turn_context.client.new_session(turn_metadata_header); let mut stream = client_session.stream(prompt).await?; loop { let maybe_event = stream.next().await; diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index c5ac0d8f85..9bd4c87253 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -139,6 +139,7 @@ pub use exec_policy::check_execpolicy_for_warnings; pub use exec_policy::load_exec_policy; pub use safety::get_platform_sandbox; pub use tools::spec::parse_tool_input_schema; +pub use turn_metadata::build_turn_metadata_header; // Re-export the protocol types from the standalone `codex-protocol` crate so existing // `codex_core::protocol::...` references continue to work across the workspace. pub use codex_protocol::protocol; diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs index 58b394a2f2..6d4878c7a3 100644 --- a/codex-rs/core/src/turn_metadata.rs +++ b/codex-rs/core/src/turn_metadata.rs @@ -20,7 +20,7 @@ struct TurnMetadata { workspaces: BTreeMap, } -pub(crate) async fn build_turn_metadata_header(cwd: &Path) -> Option { +pub async fn build_turn_metadata_header(cwd: &Path) -> Option { let repo_root = get_git_repo_root(cwd)?; let (latest_git_commit_hash, associated_remote_urls) = tokio::join!( diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 86e511c472..fd65bcf32a 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -408,88 +408,14 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() responses::ev_response_created("resp-1"), responses::ev_completed("resp-1"), ]); - let provider = ModelProviderInfo { - name: "mock".into(), - base_url: Some(format!("{}/v1", server.uri())), - env_key: None, - env_key_instructions: None, - experimental_bearer_token: None, - wire_api: WireApi::Responses, - query_params: None, - http_headers: None, - env_http_headers: None, - request_max_retries: Some(0), - stream_max_retries: Some(0), - stream_idle_timeout_ms: Some(5_000), - requires_openai_auth: false, - supports_websockets: false, - }; - let codex_home = TempDir::new().expect("failed to create TempDir"); - let mut config = load_default_config_for_test(&codex_home).await; - config.model_provider_id = provider.name.clone(); - config.model_provider = provider.clone(); - let effort = config.model_reasoning_effort; - let summary = config.model_reasoning_summary; - let model = ModelsManager::get_model_offline(config.model.as_deref()); - config.model = Some(model.clone()); - let config = Arc::new(config); - - let conversation_id = ThreadId::new(); - let auth_mode = AuthMode::Chatgpt; - let session_source = - SessionSource::SubAgent(SubAgentSource::Other("turn-metadata-e2e".to_string())); - let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); - let otel_manager = OtelManager::new( - conversation_id, - model.as_str(), - model_info.slug.as_str(), - None, - Some("test@test.com".to_string()), - Some(auth_mode), - false, - "test".to_string(), - session_source.clone(), - ); - - let client = ModelClient::new( - Arc::clone(&config), - None, - model_info, - otel_manager, - provider, - effort, - summary, - conversation_id, - session_source, - TransportManager::new(), - ); - - let workspace = TempDir::new().expect("workspace tempdir"); - let cwd = workspace.path(); - - let mut prompt = Prompt::default(); - prompt.input = vec![ResponseItem::Message { - id: None, - role: "user".into(), - content: vec![ContentItem::InputText { - text: "hello".into(), - }], - end_turn: None, - phase: None, - }]; + let test = test_codex().build(&server).await.expect("build test codex"); + let cwd = test.cwd_path(); let first_request = responses::mount_sse_once(&server, response_body.clone()).await; - let mut first_session = client.new_session(Some(cwd.to_path_buf())); - let mut first_stream = first_session - .stream(&prompt) + test.submit_turn("hello") .await - .expect("stream first turn"); - while let Some(event) = first_stream.next().await { - if matches!(event, Ok(ResponseEvent::Completed { .. })) { - break; - } - } + .expect("submit first turn prompt"); assert_eq!( first_request .single_request() @@ -539,21 +465,13 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() .trim() .to_string(); - let repo_root = std::fs::canonicalize(cwd) - .unwrap_or_else(|_| cwd.to_path_buf()) - .to_string_lossy() - .into_owned(); let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5); loop { let request_recorder = responses::mount_sse_once(&server, response_body.clone()).await; - let mut session = client.new_session(Some(cwd.to_path_buf())); tokio::time::sleep(std::time::Duration::from_millis(50)).await; - let mut stream = session.stream(&prompt).await.expect("stream post-git turn"); - while let Some(event) = stream.next().await { - if matches!(event, Ok(ResponseEvent::Completed { .. })) { - break; - } - } + test.submit_turn("hello") + .await + .expect("submit post-git turn prompt"); let maybe_header = request_recorder .single_request() @@ -561,11 +479,14 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() if let Some(header_value) = maybe_header { let parsed: serde_json::Value = serde_json::from_str(&header_value) .expect("x-codex-turn-metadata should be valid JSON"); - let workspace = parsed + let workspaces = parsed .get("workspaces") .and_then(serde_json::Value::as_object) - .and_then(|workspaces| workspaces.get(&repo_root)) - .expect("metadata should include cwd repo root workspace entry"); + .expect("metadata should include workspaces"); + let workspace = workspaces + .values() + .next() + .expect("metadata should include at least one workspace entry"); assert_eq!( workspace From 968c029471dc13ad24095f7cb87cbb19a5bf172d Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Tue, 3 Feb 2026 22:29:52 -0800 Subject: [PATCH 132/139] fix(core) updated request_rule guidance (#10379) ## Summary Update guidance for request_rule ## Testing - [x] Unit tests pass --- codex-rs/protocol/src/models.rs | 2 +- .../approval_policy/on_request_rule.md | 31 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index fe63666e38..4231423a63 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -249,7 +249,7 @@ impl DeveloperInstructions { match command_prefixes { Some(prefixes) => { format!( - "{APPROVAL_POLICY_ON_REQUEST_RULE}\nApproved command prefixes:\n{prefixes}" + "{APPROVAL_POLICY_ON_REQUEST_RULE}\n## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}" ) } None => APPROVAL_POLICY_ON_REQUEST_RULE.to_string(), diff --git a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md index 336f1fb748..2e9080664d 100644 --- a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md +++ b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md @@ -37,25 +37,20 @@ While commands are running inside the sandbox, here are some scenarios that will - You need to run a GUI app (e.g., open/xdg-open/osascript) to open browsers or files. - If you run a command that is important to solving the user's query, but it fails because of sandboxing, rerun the command with `require_escalated`. ALWAYS proceed to use the `sandbox_permissions` and `justification` parameters. do not message the user before requesting approval for the command. - You are about to take a potentially destructive action such as an `rm` or `git reset` that the user did not explicitly ask for. - -Only run commands that require approval if it is absolutely necessary to solve the user's query, don't try and circumvent approvals by using other tools. +- Be judicious with escalating, but if completing the user's request requires it, you should do so - don't try and circumvent approvals by using other tools. ## prefix_rule guidance -When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user in the future without re-requesting escalation. It should be categorical and reasonably scoped to similar capabilities. You MUST NOT pass the entire command into `prefix_rule`. +When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user in the future without re-requesting escalation. It should be categorical and reasonably scoped to similar capabilities. You should rarely pass the entire command into `prefix_rule`. + +### Banned prefix_rules +NEVER provide a prefix_rule argument for destructive commands like rm. +Do not provide a prefix rule if your command uses a heredoc or herestring. + +### Examples +Good examples of prefixes: +- ["npm", "run", "dev"] +- ["gh", "pr", "check"] +- ["pytest"] +- ["cargo", "test"] - -["npm", "run", "dev"] - - -["gh", "pr", "checks"] - - -["pytest"] - - -["cargo", "test", "-p", "codex-app-server"] - -["cargo", "test"] - - From 8f17b37d0663c2e83ac4ecb502eec123f5924527 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Wed, 4 Feb 2026 00:44:32 -0800 Subject: [PATCH 133/139] fix(core) Request Rule guidance tweak (#10598) ## Summary Forgot to include this tweak. ## Testing - [x] Unit tests pass --- .../prompts/permissions/approval_policy/on_request_rule.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md index 2e9080664d..96d962d12d 100644 --- a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md +++ b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md @@ -25,9 +25,9 @@ IMPORTANT: To request approval to execute a command that will require escalated - Provide the `sandbox_permissions` parameter with the value `"require_escalated"` - Include a short question asking the user if they want to allow the action in `justification` parameter. e.g. "Do you want to download and install dependencies for this project?" -- Suggest a `prefix_rule` - this will be shown to the user with an option to persist the rule approval for future sessions. +- Optionally suggest a `prefix_rule` - this will be shown to the user with an option to persist the rule approval for future sessions. -If you run a command that is important to solving the user's query, but it fails because of sandboxing, rerun the command with "require_escalated". ALWAYS proceed to use the `justification` and `prefix_rule` parameters - do not message the user before requesting approval for the command. +If you run a command that is important to solving the user's query, but it fails because of sandboxing, rerun the command with "require_escalated". ALWAYS proceed to use the `justification` parameter - do not message the user before requesting approval for the command. ## When to request escalation @@ -44,8 +44,9 @@ While commands are running inside the sandbox, here are some scenarios that will When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user in the future without re-requesting escalation. It should be categorical and reasonably scoped to similar capabilities. You should rarely pass the entire command into `prefix_rule`. ### Banned prefix_rules +Avoid requesting overly broad prefixes that the user would be ill-advised to approve. For example, do not request ["python3"], ["python", "-"], or other similar prefixes. NEVER provide a prefix_rule argument for destructive commands like rm. -Do not provide a prefix rule if your command uses a heredoc or herestring. +NEVER provide a prefix_rule if your command uses a heredoc or herestring. ### Examples Good examples of prefixes: From 100eb6e6f0067f75b2e964c84769c5b9fae680a6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 09:27:24 +0000 Subject: [PATCH 134/139] Prefer state DB thread listings before filesystem (#10544) Summary - add Cursor/ThreadsPage conversions so state DB listings can be mapped back into the rollout list model - make recorder list helpers query the state DB first (archived flag included) and only fall back to file traversal if needed, along with populating head bytes lazily - add extensive tests to ensure the DB path is honored for active and archived threads and that the fallback works Testing - Not run (not requested) Screenshot 2026-02-03 at 20 42 33 --- codex-rs/core/src/rollout/list.rs | 9 +- codex-rs/core/src/rollout/recorder.rs | 144 ++++++++++++-------- codex-rs/core/src/rollout/tests.rs | 187 ++++++++++++++++++++++++++ codex-rs/core/src/state_db.rs | 53 ++++++++ codex-rs/state/src/extract.rs | 128 ++++++++++++++++-- 5 files changed, 457 insertions(+), 64 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 3f057649a6..5e7c1aa350 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -261,6 +261,14 @@ impl<'de> serde::Deserialize<'de> for Cursor { } } +impl From for Cursor { + fn from(anchor: codex_state::Anchor) -> Self { + let ts = OffsetDateTime::from_unix_timestamp(anchor.ts.timestamp()) + .unwrap_or(OffsetDateTime::UNIX_EPOCH); + Self::new(ts, anchor.id) + } +} + /// Retrieve recorded thread file paths with token pagination. The returned `next_cursor` /// can be supplied on the next call to resume after the last returned item, resilient to /// concurrent new sessions being appended. Ordering is stable by the requested sort key @@ -989,7 +997,6 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result, default_provider: &str, ) -> std::io::Result { - let stage = "list_threads"; - let page = get_threads( + Self::list_threads_with_db_fallback( codex_home, page_size, cursor, @@ -129,35 +131,9 @@ impl RolloutRecorder { allowed_sources, model_providers, default_provider, - ) - .await?; - - // TODO(jif): drop after sqlite migration phase 1 - let state_db_ctx = state_db::open_if_present(codex_home, default_provider).await; - if let Some(db_ids) = state_db::list_thread_ids_db( - state_db_ctx.as_deref(), - codex_home, - page_size, - cursor, - sort_key, - allowed_sources, - model_providers, false, - stage, ) .await - { - if page.items.len() != db_ids.len() { - state_db::record_discrepancy(stage, "bad_len"); - return Ok(page); - } - for (id, item) in db_ids.iter().zip(page.items.iter()) { - if !item.path.display().to_string().contains(&id.to_string()) { - state_db::record_discrepancy(stage, "bad_id"); - } - } - } - Ok(page) } /// List archived threads (rollout files) under the archived sessions directory. @@ -170,25 +146,32 @@ impl RolloutRecorder { model_providers: Option<&[String]>, default_provider: &str, ) -> std::io::Result { - let stage = "list_archived_threads"; - let root = codex_home.join(ARCHIVED_SESSIONS_SUBDIR); - let page = get_threads_in_root( - root, + Self::list_threads_with_db_fallback( + codex_home, page_size, cursor, sort_key, - ThreadListConfig { - allowed_sources, - model_providers, - default_provider, - layout: ThreadListLayout::Flat, - }, + allowed_sources, + model_providers, + default_provider, + true, ) - .await?; + .await + } - // TODO(jif): drop after sqlite migration phase 1 + #[allow(clippy::too_many_arguments)] + async fn list_threads_with_db_fallback( + codex_home: &Path, + page_size: usize, + cursor: Option<&Cursor>, + sort_key: ThreadSortKey, + allowed_sources: &[SessionSource], + model_providers: Option<&[String]>, + default_provider: &str, + archived: bool, + ) -> std::io::Result { let state_db_ctx = state_db::open_if_present(codex_home, default_provider).await; - if let Some(db_ids) = state_db::list_thread_ids_db( + if let Some(db_page) = state_db::list_threads_db( state_db_ctx.as_deref(), codex_home, page_size, @@ -196,22 +179,42 @@ impl RolloutRecorder { sort_key, allowed_sources, model_providers, - true, - stage, + archived, ) .await { - if page.items.len() != db_ids.len() { - state_db::record_discrepancy(stage, "bad_len"); - return Ok(page); - } - for (id, item) in db_ids.iter().zip(page.items.iter()) { - if !item.path.display().to_string().contains(&id.to_string()) { - state_db::record_discrepancy(stage, "bad_id"); - } - } + let mut page: ThreadsPage = db_page.into(); + populate_thread_heads(page.items.as_mut_slice()).await; + return Ok(page); } - Ok(page) + + if archived { + let root = codex_home.join(ARCHIVED_SESSIONS_SUBDIR); + return get_threads_in_root( + root, + page_size, + cursor, + sort_key, + ThreadListConfig { + allowed_sources, + model_providers, + default_provider, + layout: ThreadListLayout::Flat, + }, + ) + .await; + } + + get_threads( + codex_home, + page_size, + cursor, + sort_key, + allowed_sources, + model_providers, + default_provider, + ) + .await } /// Find the newest recorded thread path, optionally filtering to a matching cwd. @@ -645,6 +648,41 @@ impl JsonlWriter { } } +impl From for ThreadsPage { + fn from(db_page: codex_state::ThreadsPage) -> Self { + let items = db_page + .items + .into_iter() + .map(|item| ThreadItem { + path: item.rollout_path, + head: Vec::new(), + created_at: Some(item.created_at.to_rfc3339_opts(SecondsFormat::Secs, true)), + updated_at: Some(item.updated_at.to_rfc3339_opts(SecondsFormat::Secs, true)), + }) + .collect(); + Self { + items, + next_cursor: db_page.next_anchor.map(Into::into), + num_scanned_files: db_page.num_scanned_rows, + reached_scan_cap: false, + } + } +} + +async fn populate_thread_heads(items: &mut [ThreadItem]) { + for item in items { + item.head = read_head_for_summary(item.path.as_path()) + .await + .unwrap_or_else(|err| { + warn!( + "failed to read rollout head from state db path: {} ({err})", + item.path.display() + ); + Vec::new() + }); + } +} + fn select_resume_path(page: &ThreadsPage, filter_cwd: Option<&Path>) -> Option { match filter_cwd { Some(cwd) => page.items.iter().find_map(|item| { diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index ee750b126d..e42e186157 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -7,6 +7,7 @@ use std::fs::{self}; use std::io::Write; use std::path::Path; +use chrono::TimeZone; use pretty_assertions::assert_eq; use tempfile::TempDir; use time::Duration; @@ -22,6 +23,7 @@ use crate::rollout::list::ThreadItem; use crate::rollout::list::ThreadSortKey; use crate::rollout::list::ThreadsPage; use crate::rollout::list::get_threads; +use crate::rollout::recorder::RolloutRecorder; use crate::rollout::rollout_date_parts; use anyhow::Result; use codex_protocol::ThreadId; @@ -45,6 +47,191 @@ fn provider_vec(providers: &[&str]) -> Vec { .collect() } +async fn insert_state_db_thread( + home: &Path, + thread_id: ThreadId, + rollout_path: &Path, + archived: bool, +) { + let runtime = + codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string(), None) + .await + .expect("state db should initialize"); + let created_at = chrono::Utc + .with_ymd_and_hms(2025, 1, 3, 12, 0, 0) + .single() + .expect("valid datetime"); + let mut builder = codex_state::ThreadMetadataBuilder::new( + thread_id, + rollout_path.to_path_buf(), + created_at, + SessionSource::Cli, + ); + builder.model_provider = Some(TEST_PROVIDER.to_string()); + builder.cwd = home.to_path_buf(); + if archived { + builder.archived_at = Some(created_at); + } + let mut metadata = builder.build(TEST_PROVIDER); + metadata.has_user_event = true; + runtime + .upsert_thread(&metadata) + .await + .expect("state db upsert should succeed"); +} + +#[tokio::test] +async fn list_threads_prefers_state_db_when_available() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let fs_uuid = Uuid::from_u128(101); + write_session_file( + home, + "2025-01-03T13-00-00", + fs_uuid, + 1, + Some(SessionSource::Cli), + ) + .unwrap(); + + let db_uuid = Uuid::from_u128(102); + let db_thread_id = ThreadId::from_string(&db_uuid.to_string()).expect("valid thread id"); + let db_rollout_path = home.join(format!( + "sessions/2025/01/03/rollout-2025-01-03T12-00-00-{db_uuid}.jsonl" + )); + insert_state_db_thread(home, db_thread_id, db_rollout_path.as_path(), false).await; + + let page = RolloutRecorder::list_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + assert_eq!(page.items[0].path, db_rollout_path); +} + +#[tokio::test] +async fn list_archived_threads_prefers_state_db_when_available() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let archived_root = home.join("archived_sessions"); + fs::create_dir_all(&archived_root).unwrap(); + let fs_uuid = Uuid::from_u128(201); + let fs_path = archived_root.join(format!("rollout-2025-01-03T13-00-00-{fs_uuid}.jsonl")); + fs::write(&fs_path, "{\"type\":\"session_meta\",\"payload\":{}}\n").unwrap(); + + let db_uuid = Uuid::from_u128(202); + let db_thread_id = ThreadId::from_string(&db_uuid.to_string()).expect("valid thread id"); + let db_rollout_path = + archived_root.join(format!("rollout-2025-01-03T12-00-00-{db_uuid}.jsonl")); + insert_state_db_thread(home, db_thread_id, db_rollout_path.as_path(), true).await; + + let page = RolloutRecorder::list_archived_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("archived thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + assert_eq!(page.items[0].path, db_rollout_path); +} + +#[tokio::test] +async fn list_threads_db_excludes_archived_entries() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let sessions_root = home.join("sessions/2025/01/03"); + let archived_root = home.join("archived_sessions"); + fs::create_dir_all(&sessions_root).unwrap(); + fs::create_dir_all(&archived_root).unwrap(); + + let active_uuid = Uuid::from_u128(211); + let active_thread_id = + ThreadId::from_string(&active_uuid.to_string()).expect("valid active thread id"); + let active_rollout_path = + sessions_root.join(format!("rollout-2025-01-03T12-00-00-{active_uuid}.jsonl")); + insert_state_db_thread(home, active_thread_id, active_rollout_path.as_path(), false).await; + + let archived_uuid = Uuid::from_u128(212); + let archived_thread_id = + ThreadId::from_string(&archived_uuid.to_string()).expect("valid archived thread id"); + let archived_rollout_path = + archived_root.join(format!("rollout-2025-01-03T11-00-00-{archived_uuid}.jsonl")); + insert_state_db_thread( + home, + archived_thread_id, + archived_rollout_path.as_path(), + true, + ) + .await; + + let page = RolloutRecorder::list_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + assert_eq!(page.items[0].path, active_rollout_path); +} + +#[tokio::test] +async fn list_threads_falls_back_to_files_when_state_db_is_unavailable() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let fs_uuid = Uuid::from_u128(301); + write_session_file( + home, + "2025-01-03T13-00-00", + fs_uuid, + 1, + Some(SessionSource::Cli), + ) + .unwrap(); + + let page = RolloutRecorder::list_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + let file_name = page.items[0] + .path + .file_name() + .and_then(|value| value.to_str()) + .expect("rollout file name should be utf8"); + assert!( + file_name.contains(&fs_uuid.to_string()), + "expected file path from filesystem listing, got: {file_name}" + ); +} + #[test] fn rollout_date_parts_extracts_directory_components() { let file_name = OsStr::new("rollout-2025-03-01T09-00-00-123.jsonl"); diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index ff95ed946f..7a0894a327 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -181,6 +181,59 @@ pub async fn list_thread_ids_db( } } +/// List thread metadata from SQLite without rollout directory traversal. +#[allow(clippy::too_many_arguments)] +pub async fn list_threads_db( + context: Option<&codex_state::StateRuntime>, + codex_home: &Path, + page_size: usize, + cursor: Option<&Cursor>, + sort_key: ThreadSortKey, + allowed_sources: &[SessionSource], + model_providers: Option<&[String]>, + archived: bool, +) -> Option { + let ctx = context?; + if ctx.codex_home() != codex_home { + warn!( + "state db codex_home mismatch: expected {}, got {}", + ctx.codex_home().display(), + codex_home.display() + ); + } + + let anchor = cursor_to_anchor(cursor); + let allowed_sources: Vec = allowed_sources + .iter() + .map(|value| match serde_json::to_value(value) { + Ok(Value::String(s)) => s, + Ok(other) => other.to_string(), + Err(_) => String::new(), + }) + .collect(); + let model_providers = model_providers.map(<[String]>::to_vec); + match ctx + .list_threads( + page_size, + anchor.as_ref(), + match sort_key { + ThreadSortKey::CreatedAt => codex_state::SortKey::CreatedAt, + ThreadSortKey::UpdatedAt => codex_state::SortKey::UpdatedAt, + }, + allowed_sources.as_slice(), + model_providers.as_deref(), + archived, + ) + .await + { + Ok(page) => Some(page), + Err(err) => { + warn!("state db list_threads failed: {err}"); + None + } + } +} + /// Look up the rollout path for a thread id using SQLite. pub async fn find_rollout_path_by_id( context: Option<&codex_state::StateRuntime>, diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index ad40118086..bfcd75416f 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -3,14 +3,19 @@ use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; use codex_protocol::models::is_local_image_close_tag_text; use codex_protocol::models::is_local_image_open_tag_text; +use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::TurnContextItem; +use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; use codex_protocol::protocol::USER_MESSAGE_BEGIN; use serde::Serialize; use serde_json::Value; +const USER_INSTRUCTIONS_PREFIX: &str = "# AGENTS.md instructions for "; +const TURN_ABORTED_OPEN_TAG: &str = ""; + /// Apply a rollout item to the metadata structure. pub fn apply_rollout_item( metadata: &mut ThreadMetadata, @@ -74,12 +79,46 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } fn apply_response_item(metadata: &mut ThreadMetadata, item: &ResponseItem) { - if let Some(text) = extract_user_message_text(item) { - metadata.has_user_event = true; - if metadata.title.is_empty() { - metadata.title = text; - } + if !is_user_response_item(item) { + return; } + metadata.has_user_event = true; + if metadata.title.is_empty() + && let Some(text) = extract_user_message_text(item) + { + metadata.title = text; + } +} + +// TODO(jif) unify once the discussion is settled +fn is_user_response_item(item: &ResponseItem) -> bool { + let ResponseItem::Message { role, content, .. } = item else { + return false; + }; + role == "user" + && !is_user_instructions(content.as_slice()) + && !is_session_prefix_content(content.as_slice()) +} + +fn is_user_instructions(content: &[ContentItem]) -> bool { + if let [ContentItem::InputText { text }] = content { + text.starts_with(USER_INSTRUCTIONS_PREFIX) || text.starts_with(USER_INSTRUCTIONS_OPEN_TAG) + } else { + false + } +} + +fn is_session_prefix_content(content: &[ContentItem]) -> bool { + if let [ContentItem::InputText { text }] = content { + is_session_prefix(text) + } else { + false + } +} + +fn is_session_prefix(text: &str) -> bool { + let lowered = text.trim_start().to_ascii_lowercase(); + lowered.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG) || lowered.starts_with(TURN_ABORTED_OPEN_TAG) } fn extract_user_message_text(item: &ResponseItem) -> Option { @@ -125,6 +164,7 @@ pub(crate) fn enum_to_string(value: &T) -> String { #[cfg(test)] mod tests { + use super::apply_rollout_item; use super::extract_user_message_text; use crate::model::ThreadMetadata; use chrono::DateTime; @@ -132,6 +172,8 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; + use codex_protocol::protocol::RolloutItem; + use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; use codex_protocol::protocol::USER_MESSAGE_BEGIN; use pretty_assertions::assert_eq; use std::path::PathBuf; @@ -158,10 +200,69 @@ mod tests { } #[test] - fn diff_fields_detects_changes() { - let id = ThreadId::from_string(&Uuid::now_v7().to_string()).expect("thread id"); + fn user_instructions_do_not_count_as_user_events() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::ResponseItem(ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: format!( + "# AGENTS.md instructions for /tmp\n\n{USER_INSTRUCTIONS_OPEN_TAG}test" + ), + }], + end_turn: None, + phase: None, + }); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!(metadata.has_user_event, false); + assert_eq!(metadata.title, ""); + } + + #[test] + fn session_prefix_messages_do_not_count_as_user_events() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::ResponseItem(ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: "\n {\"cwd\":\"/tmp\"}" + .to_string(), + }], + end_turn: None, + phase: None, + }); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!(metadata.has_user_event, false); + assert_eq!(metadata.title, ""); + } + + #[test] + fn image_only_user_messages_still_count_as_user_events() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::ResponseItem(ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputImage { + image_url: "https://example.com/image.png".to_string(), + }], + end_turn: None, + phase: None, + }); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!(metadata.has_user_event, true); + assert_eq!(metadata.title, ""); + } + + fn metadata_for_test() -> ThreadMetadata { + let id = ThreadId::from_string(&Uuid::from_u128(42).to_string()).expect("thread id"); let created_at = DateTime::::from_timestamp(1_735_689_600, 0).expect("timestamp"); - let base = ThreadMetadata { + ThreadMetadata { id, rollout_path: PathBuf::from("/tmp/a.jsonl"), created_at, @@ -169,7 +270,7 @@ mod tests { source: "cli".to_string(), model_provider: "openai".to_string(), cwd: PathBuf::from("/tmp"), - title: "hello".to_string(), + title: String::new(), sandbox_policy: "read-only".to_string(), approval_mode: "on-request".to_string(), tokens_used: 1, @@ -178,7 +279,14 @@ mod tests { git_sha: None, git_branch: None, git_origin_url: None, - }; + } + } + + #[test] + fn diff_fields_detects_changes() { + let mut base = metadata_for_test(); + base.id = ThreadId::from_string(&Uuid::now_v7().to_string()).expect("thread id"); + base.title = "hello".to_string(); let mut other = base.clone(); other.tokens_used = 2; other.title = "world".to_string(); From 3d8deeea4b60e1c5ed98bb55779dee3ea96b73e0 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 10:57:58 +0000 Subject: [PATCH 135/139] fix: single transaction for dyn tools injection (#10614) --- codex-rs/state/src/runtime.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 9a750f1d5d..c64b7d56c6 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -419,6 +419,7 @@ ON CONFLICT(id) DO UPDATE SET return Ok(()); } let thread_id = thread_id.to_string(); + let mut tx = self.pool.begin().await?; for (idx, tool) in tools.iter().enumerate() { let position = i64::try_from(idx).unwrap_or(i64::MAX); let input_schema = serde_json::to_string(&tool.input_schema)?; @@ -439,9 +440,10 @@ ON CONFLICT(thread_id, position) DO NOTHING .bind(tool.name.as_str()) .bind(tool.description.as_str()) .bind(input_schema) - .execute(self.pool.as_ref()) + .execute(&mut *tx) .await?; } + tx.commit().await?; Ok(()) } From 1eb21e279e9282b5b894bcfcf890d390cae7d643 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Wed, 4 Feb 2026 11:09:48 +0000 Subject: [PATCH 136/139] Requirements: add source to constrained requirement values (#10568) If we want to build `/debug-config`, we'll need to know the requirements sources that supplied the values. This PR adds those sources such that we can render them in the UI. --- codex-rs/core/src/config/mod.rs | 6 +- .../src/config_loader/config_requirements.rs | 110 ++++++++++++++---- codex-rs/core/src/config_loader/mod.rs | 1 + 3 files changed, 93 insertions(+), 24 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 949c08724f..d4547db19f 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1588,9 +1588,9 @@ impl Config { model_provider_id, model_provider, cwd: resolved_cwd, - approval_policy: constrained_approval_policy, - sandbox_policy: constrained_sandbox_policy, - enforce_residency, + approval_policy: constrained_approval_policy.value, + sandbox_policy: constrained_sandbox_policy.value, + enforce_residency: enforce_residency.value, did_user_set_custom_approval_policy_or_sandbox_mode, forced_auto_mode_downgraded_on_windows, shell_environment_policy, diff --git a/codex-rs/core/src/config_loader/config_requirements.rs b/codex-rs/core/src/config_loader/config_requirements.rs index 478c74193e..660628c04f 100644 --- a/codex-rs/core/src/config_loader/config_requirements.rs +++ b/codex-rs/core/src/config_loader/config_requirements.rs @@ -44,25 +44,57 @@ impl fmt::Display for RequirementSource { } } +#[derive(Debug, Clone, PartialEq)] +pub struct ConstrainedWithSource { + pub value: Constrained, + pub source: Option, +} + +impl ConstrainedWithSource { + pub fn new(value: Constrained, source: Option) -> Self { + Self { value, source } + } +} + +impl std::ops::Deref for ConstrainedWithSource { + type Target = Constrained; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl std::ops::DerefMut for ConstrainedWithSource { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} + /// Normalized version of [`ConfigRequirementsToml`] after deserialization and /// normalization. #[derive(Debug, Clone, PartialEq)] pub struct ConfigRequirements { - pub approval_policy: Constrained, - pub sandbox_policy: Constrained, + pub approval_policy: ConstrainedWithSource, + pub sandbox_policy: ConstrainedWithSource, pub mcp_servers: Option>>, pub(crate) exec_policy: Option>, - pub enforce_residency: Constrained>, + pub enforce_residency: ConstrainedWithSource>, } impl Default for ConfigRequirements { fn default() -> Self { Self { - approval_policy: Constrained::allow_any_from_default(), - sandbox_policy: Constrained::allow_any(SandboxPolicy::ReadOnly), + approval_policy: ConstrainedWithSource::new( + Constrained::allow_any_from_default(), + None, + ), + sandbox_policy: ConstrainedWithSource::new( + Constrained::allow_any(SandboxPolicy::ReadOnly), + None, + ), mcp_servers: None, exec_policy: None, - enforce_residency: Constrained::allow_any(None), + enforce_residency: ConstrainedWithSource::new(Constrained::allow_any(None), None), } } } @@ -228,7 +260,7 @@ impl TryFrom for ConfigRequirements { enforce_residency, } = toml; - let approval_policy: Constrained = match allowed_approval_policies { + let approval_policy = match allowed_approval_policies { Some(Sourced { value: policies, source: requirement_source, @@ -237,7 +269,8 @@ impl TryFrom for ConfigRequirements { return Err(ConstraintError::empty_field("allowed_approval_policies")); }; - Constrained::new(initial_value, move |candidate| { + let requirement_source_for_error = requirement_source.clone(); + let constrained = Constrained::new(initial_value, move |candidate| { if policies.contains(candidate) { Ok(()) } else { @@ -245,12 +278,13 @@ impl TryFrom for ConfigRequirements { field_name: "approval_policy", candidate: format!("{candidate:?}"), allowed: format!("{policies:?}"), - requirement_source: requirement_source.clone(), + requirement_source: requirement_source_for_error.clone(), }) } - })? + })?; + ConstrainedWithSource::new(constrained, Some(requirement_source)) } - None => Constrained::allow_any_from_default(), + None => ConstrainedWithSource::new(Constrained::allow_any_from_default(), None), }; // TODO(gt): `ConfigRequirementsToml` should let the author specify the @@ -261,7 +295,7 @@ impl TryFrom for ConfigRequirements { // additional parameters. Ultimately, we should expand the config // format to allow specifying those parameters. let default_sandbox_policy = SandboxPolicy::ReadOnly; - let sandbox_policy: Constrained = match allowed_sandbox_modes { + let sandbox_policy = match allowed_sandbox_modes { Some(Sourced { value: modes, source: requirement_source, @@ -275,7 +309,8 @@ impl TryFrom for ConfigRequirements { }); }; - Constrained::new(default_sandbox_policy, move |candidate| { + let requirement_source_for_error = requirement_source.clone(); + let constrained = Constrained::new(default_sandbox_policy, move |candidate| { let mode = match candidate { SandboxPolicy::ReadOnly => SandboxModeRequirement::ReadOnly, SandboxPolicy::WorkspaceWrite { .. } => { @@ -293,12 +328,15 @@ impl TryFrom for ConfigRequirements { field_name: "sandbox_mode", candidate: format!("{mode:?}"), allowed: format!("{modes:?}"), - requirement_source: requirement_source.clone(), + requirement_source: requirement_source_for_error.clone(), }) } - })? + })?; + ConstrainedWithSource::new(constrained, Some(requirement_source)) + } + None => { + ConstrainedWithSource::new(Constrained::allow_any(default_sandbox_policy), None) } - None => Constrained::allow_any(default_sandbox_policy), }; let exec_policy = match rules { Some(Sourced { value, source }) => { @@ -313,13 +351,14 @@ impl TryFrom for ConfigRequirements { None => None, }; - let enforce_residency: Constrained> = match enforce_residency { + let enforce_residency = match enforce_residency { Some(Sourced { value: residency, source: requirement_source, }) => { let required = Some(residency); - Constrained::new(required, move |candidate| { + let requirement_source_for_error = requirement_source.clone(); + let constrained = Constrained::new(required, move |candidate| { if candidate == &required { Ok(()) } else { @@ -327,12 +366,13 @@ impl TryFrom for ConfigRequirements { field_name: "enforce_residency", candidate: format!("{candidate:?}"), allowed: format!("{required:?}"), - requirement_source: requirement_source.clone(), + requirement_source: requirement_source_for_error.clone(), }) } - })? + })?; + ConstrainedWithSource::new(constrained, Some(requirement_source)) } - None => Constrained::allow_any(None), + None => ConstrainedWithSource::new(Constrained::allow_any(None), None), }; Ok(ConfigRequirements { approval_policy, @@ -563,6 +603,34 @@ mod tests { Ok(()) } + #[test] + fn constrained_fields_store_requirement_source() -> Result<()> { + let source: ConfigRequirementsToml = from_str( + r#" + allowed_approval_policies = ["on-request"] + allowed_sandbox_modes = ["read-only"] + enforce_residency = "us" + "#, + )?; + + let source_location = RequirementSource::CloudRequirements; + let mut target = ConfigRequirementsWithSources::default(); + target.merge_unset_fields(source_location.clone(), source); + let requirements = ConfigRequirements::try_from(target)?; + + assert_eq!( + requirements.approval_policy.source, + Some(source_location.clone()) + ); + assert_eq!( + requirements.sandbox_policy.source, + Some(source_location.clone()) + ); + assert_eq!(requirements.enforce_residency.source, Some(source_location)); + + Ok(()) + } + #[test] fn deserialize_allowed_approval_policies() -> Result<()> { let toml_str = r#" diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index d8813e23d7..e9cd1c68e8 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -34,6 +34,7 @@ use toml::Value as TomlValue; pub use cloud_requirements::CloudRequirementsLoader; pub use config_requirements::ConfigRequirements; pub use config_requirements::ConfigRequirementsToml; +pub use config_requirements::ConstrainedWithSource; pub use config_requirements::McpServerIdentity; pub use config_requirements::McpServerRequirement; pub use config_requirements::RequirementSource; From 38f6c6b1141699e87fffb398372abe157e6c52c1 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 11:14:53 +0000 Subject: [PATCH 137/139] chore: simplify user message detection (#10611) We don't check anymore the response item with `user` role as they may be instructions etc --- codex-rs/core/src/rollout/list.rs | 2 +- codex-rs/core/src/rollout/recorder.rs | 2 + codex-rs/state/src/extract.rs | 119 +------------------------- 3 files changed, 6 insertions(+), 117 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 5e7c1aa350..34e144e568 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -1125,7 +1125,7 @@ async fn find_thread_path_by_id_str_in_subdir( let found = results.matches.into_iter().next().map(|m| m.full_path()); if found.is_some() { tracing::error!("state db missing rollout path for thread {id_str}"); - state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "path_mismatch"); + state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "falling_back"); } Ok(found) diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index ab017810f3..465bcbbcdf 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -187,6 +187,8 @@ impl RolloutRecorder { populate_thread_heads(page.items.as_mut_slice()).await; return Ok(page); } + tracing::error!("Falling back on rollout system"); + state_db::record_discrepancy("list_threads_with_db_fallback", "falling_back"); if archived { let root = codex_home.join(ARCHIVED_SESSIONS_SUBDIR); diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index bfcd75416f..2bf3949751 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -1,21 +1,13 @@ use crate::model::ThreadMetadata; -use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; -use codex_protocol::models::is_local_image_close_tag_text; -use codex_protocol::models::is_local_image_open_tag_text; -use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::TurnContextItem; -use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; use codex_protocol::protocol::USER_MESSAGE_BEGIN; use serde::Serialize; use serde_json::Value; -const USER_INSTRUCTIONS_PREFIX: &str = "# AGENTS.md instructions for "; -const TURN_ABORTED_OPEN_TAG: &str = ""; - /// Apply a rollout item to the metadata structure. pub fn apply_rollout_item( metadata: &mut ThreadMetadata, @@ -78,73 +70,8 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } } -fn apply_response_item(metadata: &mut ThreadMetadata, item: &ResponseItem) { - if !is_user_response_item(item) { - return; - } - metadata.has_user_event = true; - if metadata.title.is_empty() - && let Some(text) = extract_user_message_text(item) - { - metadata.title = text; - } -} - -// TODO(jif) unify once the discussion is settled -fn is_user_response_item(item: &ResponseItem) -> bool { - let ResponseItem::Message { role, content, .. } = item else { - return false; - }; - role == "user" - && !is_user_instructions(content.as_slice()) - && !is_session_prefix_content(content.as_slice()) -} - -fn is_user_instructions(content: &[ContentItem]) -> bool { - if let [ContentItem::InputText { text }] = content { - text.starts_with(USER_INSTRUCTIONS_PREFIX) || text.starts_with(USER_INSTRUCTIONS_OPEN_TAG) - } else { - false - } -} - -fn is_session_prefix_content(content: &[ContentItem]) -> bool { - if let [ContentItem::InputText { text }] = content { - is_session_prefix(text) - } else { - false - } -} - -fn is_session_prefix(text: &str) -> bool { - let lowered = text.trim_start().to_ascii_lowercase(); - lowered.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG) || lowered.starts_with(TURN_ABORTED_OPEN_TAG) -} - -fn extract_user_message_text(item: &ResponseItem) -> Option { - let ResponseItem::Message { role, content, .. } = item else { - return None; - }; - if role != "user" { - return None; - } - let texts: Vec<&str> = content - .iter() - .filter_map(|content_item| match content_item { - ContentItem::InputText { text } => Some(text.as_str()), - ContentItem::InputImage { .. } | ContentItem::OutputText { .. } => None, - }) - .filter(|text| !is_local_image_open_tag_text(text) && !is_local_image_close_tag_text(text)) - .collect(); - if texts.is_empty() { - return None; - } - let joined = texts.join("\n"); - Some( - strip_user_message_prefix(joined.as_str()) - .trim() - .to_string(), - ) +fn apply_response_item(_metadata: &mut ThreadMetadata, _item: &ResponseItem) { + // TODO (jif) } fn strip_user_message_prefix(text: &str) -> &str { @@ -165,7 +92,6 @@ pub(crate) fn enum_to_string(value: &T) -> String { #[cfg(test)] mod tests { use super::apply_rollout_item; - use super::extract_user_message_text; use crate::model::ThreadMetadata; use chrono::DateTime; use chrono::Utc; @@ -174,31 +100,11 @@ mod tests { use codex_protocol::models::ResponseItem; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; - use codex_protocol::protocol::USER_MESSAGE_BEGIN; + use pretty_assertions::assert_eq; use std::path::PathBuf; use uuid::Uuid; - #[test] - fn extracts_user_message_text() { - let item = ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ - ContentItem::InputText { - text: format!(" {USER_MESSAGE_BEGIN}actual question"), - }, - ContentItem::InputImage { - image_url: "https://example.com/image.png".to_string(), - }, - ], - end_turn: None, - phase: None, - }; - let actual = extract_user_message_text(&item); - assert_eq!(actual.as_deref(), Some("actual question")); - } - #[test] fn user_instructions_do_not_count_as_user_events() { let mut metadata = metadata_for_test(); @@ -240,25 +146,6 @@ mod tests { assert_eq!(metadata.title, ""); } - #[test] - fn image_only_user_messages_still_count_as_user_events() { - let mut metadata = metadata_for_test(); - let item = RolloutItem::ResponseItem(ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputImage { - image_url: "https://example.com/image.png".to_string(), - }], - end_turn: None, - phase: None, - }); - - apply_rollout_item(&mut metadata, &item, "test-provider"); - - assert_eq!(metadata.has_user_event, true); - assert_eq!(metadata.title, ""); - } - fn metadata_for_test() -> ThreadMetadata { let id = ThreadId::from_string(&Uuid::from_u128(42).to_string()).expect("thread id"); let created_at = DateTime::::from_timestamp(1_735_689_600, 0).expect("timestamp"); From 61aecdde663047aee4b9f63a4ed34bc466bc6e4b Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 13:01:17 +0000 Subject: [PATCH 138/139] fix: make sure file exist in `find_thread_path_by_id_str_in_subdir` (#10618) --- codex-rs/core/src/rollout/list.rs | 15 ++++++++----- codex-rs/core/src/rollout/tests.rs | 21 +++++++++++++++++++ .../core/tests/suite/rollout_list_find.rs | 8 ++++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 34e144e568..2cac898d42 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -1091,17 +1091,22 @@ async fn find_thread_path_by_id_str_in_subdir( let state_db_ctx = state_db::open_if_present(codex_home, "").await; if let Some(state_db_ctx) = state_db_ctx.as_deref() && let Ok(thread_id) = ThreadId::from_string(id_str) - { - let db_path = state_db::find_rollout_path_by_id( + && let Some(db_path) = state_db::find_rollout_path_by_id( Some(state_db_ctx), thread_id, archived_only, "find_path_query", ) - .await; - if db_path.is_some() { - return Ok(db_path); + .await + { + if tokio::fs::try_exists(&db_path).await.unwrap_or(false) { + return Ok(Some(db_path)); } + tracing::error!( + "state db returned stale rollout path for thread {id_str}: {}", + db_path.display() + ); + state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "stale_db_path"); } let mut root = codex_home.to_path_buf(); diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index e42e186157..89df2897db 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -232,6 +232,27 @@ async fn list_threads_falls_back_to_files_when_state_db_is_unavailable() { ); } +#[tokio::test] +async fn find_thread_path_falls_back_when_db_path_is_stale() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let uuid = Uuid::from_u128(302); + let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id"); + let ts = "2025-01-03T13-00-00"; + write_session_file(home, ts, uuid, 1, Some(SessionSource::Cli)).unwrap(); + let fs_rollout_path = home.join(format!("sessions/2025/01/03/rollout-{ts}-{uuid}.jsonl")); + + let stale_db_path = home.join(format!( + "sessions/2099/01/01/rollout-2099-01-01T00-00-00-{uuid}.jsonl" + )); + insert_state_db_thread(home, thread_id, stale_db_path.as_path(), false).await; + + let found = crate::rollout::find_thread_path_by_id_str(home, &uuid.to_string()) + .await + .expect("lookup should succeed"); + assert_eq!(found, Some(fs_rollout_path)); +} + #[test] fn rollout_date_parts_extracts_directory_components() { let file_name = OsStr::new("rollout-2025-03-01T09-00-00-123.jsonl"); diff --git a/codex-rs/core/tests/suite/rollout_list_find.rs b/codex-rs/core/tests/suite/rollout_list_find.rs index 8786c9be21..5b545384b9 100644 --- a/codex-rs/core/tests/suite/rollout_list_find.rs +++ b/codex-rs/core/tests/suite/rollout_list_find.rs @@ -104,9 +104,11 @@ async fn find_prefers_sqlite_path_by_id() { let home = TempDir::new().unwrap(); let id = Uuid::new_v4(); let thread_id = ThreadId::from_string(&id.to_string()).unwrap(); - let db_path = home - .path() - .join("sessions/2030/12/30/rollout-2030-12-30T00-00-00-db.jsonl"); + let db_path = home.path().join(format!( + "sessions/2030/12/30/rollout-2030-12-30T00-00-00-{id}.jsonl" + )); + std::fs::create_dir_all(db_path.parent().unwrap()).unwrap(); + std::fs::write(&db_path, "").unwrap(); write_minimal_rollout_with_id(home.path(), id); upsert_thread_metadata(home.path(), thread_id, db_path.clone()).await; From aab60a55f1ca2aac2472fbc407b4dbc82c61d522 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 13:01:24 +0000 Subject: [PATCH 139/139] nit: cleaning (#10619) --- codex-rs/state/src/extract.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 2bf3949751..753d8e426d 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -71,7 +71,7 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } fn apply_response_item(_metadata: &mut ThreadMetadata, _item: &ResponseItem) { - // TODO (jif) + // Title and has_user_event are derived from EventMsg::UserMessage only. } fn strip_user_message_prefix(text: &str) -> &str { @@ -98,23 +98,23 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; + use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; - use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; + use codex_protocol::protocol::USER_MESSAGE_BEGIN; + use codex_protocol::protocol::UserMessageEvent; use pretty_assertions::assert_eq; use std::path::PathBuf; use uuid::Uuid; #[test] - fn user_instructions_do_not_count_as_user_events() { + fn response_item_user_messages_do_not_set_title_or_has_user_event() { let mut metadata = metadata_for_test(); let item = RolloutItem::ResponseItem(ResponseItem::Message { id: None, role: "user".to_string(), content: vec![ContentItem::InputText { - text: format!( - "# AGENTS.md instructions for /tmp\n\n{USER_INSTRUCTIONS_OPEN_TAG}test" - ), + text: "hello from response item".to_string(), }], end_turn: None, phase: None, @@ -127,23 +127,19 @@ mod tests { } #[test] - fn session_prefix_messages_do_not_count_as_user_events() { + fn event_msg_user_messages_set_title_and_has_user_event() { let mut metadata = metadata_for_test(); - let item = RolloutItem::ResponseItem(ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "\n {\"cwd\":\"/tmp\"}" - .to_string(), - }], - end_turn: None, - phase: None, - }); + let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + message: format!("{USER_MESSAGE_BEGIN} actual user request"), + images: Some(vec![]), + local_images: vec![], + text_elements: vec![], + })); apply_rollout_item(&mut metadata, &item, "test-provider"); - assert_eq!(metadata.has_user_event, false); - assert_eq!(metadata.title, ""); + assert_eq!(metadata.has_user_event, true); + assert_eq!(metadata.title, "actual user request"); } fn metadata_for_test() -> ThreadMetadata {